├── .gitignore ├── LICENSE ├── README.md ├── _extra.py ├── app_camera.py ├── app_explorer.py ├── app_launcher.py ├── app_system_info.py ├── boot.py ├── config.py ├── docs └── m5stickv_computer.png ├── framework.py ├── m5stickv_system.py ├── my_pmu.py ├── others ├── alert_64x60.jpg └── explorer_standalone.py ├── res ├── icons │ ├── arrow_top_24x23.jpg │ ├── battery │ │ ├── 0.jpg │ │ ├── 100.jpg │ │ ├── 20.jpg │ │ ├── 40.jpg │ │ ├── 60.jpg │ │ ├── 80.jpg │ │ ├── charging_0.jpg │ │ ├── charging_100.jpg │ │ ├── charging_20.jpg │ │ ├── charging_40.jpg │ │ ├── charging_60.jpg │ │ └── charging_80.jpg │ ├── battery_64x60.jpg │ ├── brightness_64x60.jpg │ ├── camera_64x60.jpg │ ├── code_64x60.jpg │ ├── memory_card_64x60.jpg │ ├── microphone_64x60.jpg │ ├── music_64x60.jpg │ ├── power_64x60.jpg │ ├── python_64x60.jpg │ ├── reboot_64x60.jpg │ ├── settings_64x60.jpg │ ├── speedometer_64x60.jpg │ ├── tools_64x60.jpg │ └── video_64x60.jpg ├── provision.jpg └── provision_old.jpg ├── resource.py ├── super_mario.wav └── traceback.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # M5StickVComputer 2 | 3 | A pure Python written Operating System for M5StickV (https://docs.m5stack.com/#/en/core/m5stickv) 4 | 5 | 6 | 7 | ![m5stickv_computer](docs/m5stickv_computer.png) 8 | 9 | DONE: 10 | 11 | * Camera preview with face recognization 12 | * Partially functional file explorer 13 | * Buttons exvent handling 14 | * Icon switching animations 15 | 16 | TODO: 17 | 18 | * Full functional file explorer 19 | * Video recording 20 | * Microphone recording 21 | * Wav audio player 22 | * Settings (brightness, power saving, etc) 23 | 24 | -------------------------------------------------------------------------------- /_extra.py: -------------------------------------------------------------------------------- 1 | 2 | root_files = os.listdir('/') 3 | for f in root_files: 4 | fs_path = '/' + f 5 | fs_stat = uos.statvfs(fs_path) 6 | bs1 = fs_stat[0] 7 | bs2 = fs_stat[1] 8 | total_blocks = fs_stat[2] 9 | free_blocks = fs_stat[3] 10 | print("fs: %s, total: %s, free: %s" % 11 | (fs_path, sizeof_fmt(bs1 * total_blocks), sizeof_fmt(bs2 * free_blocks))) 12 | # uos.statvfs('/sd') 13 | # (32768, 32768, 475520, 472555, 472555, 0, 0, 0, 0, 255) 14 | 15 | 16 | try: 17 | # img = image.Image("/sd/win98_240x135.jpg") 18 | img = image.Image("/flash/startup.jpg") 19 | print("240") 20 | lcd.display(img) 21 | del img 22 | print("display 240") 23 | # eggfly mod 24 | print("brfore, mem_free:", gc.mem_free()) 25 | screen_canvas = image.Image() 26 | print("after, mem_free:", gc.mem_free()) 27 | print("screen_canvas info:", screen_canvas.width(), screen_canvas.height()) 28 | print("screen_canvas") 29 | # screen_canvas.draw_rectangle(0,0,screen_canvas.width(), screen_canvas.height(), lcd.WHITE, fill=True) 30 | icon_power = image.Image("/sd/icons/power.jpg") 31 | print("icon_power info:", icon_power.width(), icon_power.height()) 32 | screen_canvas.draw_image(icon_power, 20, 30) 33 | del icon_power 34 | print("icon_power, mem_free:", gc.mem_free()) 35 | gc.collect() 36 | print("icon_power2, mem_free:", gc.mem_free()) 37 | icon_reboot = image.Image("/sd/icons/reboot.jpg") 38 | screen_canvas.draw_image(icon_reboot, 110, 30) 39 | del icon_reboot 40 | print("icon_reboot, mem_free:", gc.mem_free()) 41 | lcd.display(screen_canvas) 42 | # time.sleep(1) 43 | except Exception as e: 44 | print(e) 45 | lcd.draw_string(lcd.width() // 2 - 100, lcd.height() // 2 - 4, 46 | "Error: Cannot find start.jpg", lcd.WHITE, lcd.RED) 47 | 48 | wav_dev = I2S(I2S.DEVICE_0) 49 | # i2s0:(sampling rate=0, sampling points=1024) 50 | 51 | print(wav_dev) 52 | """ 53 | [MAIXPY]: result = 0 54 | [MAIXPY]: numchannels = 1 55 | [MAIXPY]: samplerate = 44100 56 | [MAIXPY]: byterate = 88200 57 | [MAIXPY]: blockalign = 2 58 | [MAIXPY]: bitspersample = 16 59 | [MAIXPY]: datasize = 246960 60 | True 61 | [1, 44100, 88200, 2, 16, 246960] 62 | """ 63 | try: 64 | # player = audio.Audio(path = "/flash/ding.wav") 65 | player = audio.Audio(path="/sd/super_mario.wav") 66 | player.volume(0) # todo change this 67 | wav_info = player.play_process(wav_dev) 68 | wav_dev.channel_config(wav_dev.CHANNEL_1, I2S.TRANSMITTER, 69 | resolution=I2S.RESOLUTION_16_BIT, align_mode=I2S.STANDARD_MODE) 70 | print(wav_info) 71 | wav_dev.set_sample_rate(wav_info[1]) 72 | while True: 73 | ret = player.play() 74 | if ret == None: 75 | break 76 | elif ret == 0: 77 | break 78 | player.finish() 79 | except Exception as e: 80 | print(e) 81 | print("ignored") 82 | pass 83 | 84 | # time.sleep(1.5) # Delay for few seconds to see the start-up screen :p 85 | 86 | but_stu = 1 87 | 88 | current_dir_files = os.listdir("/sd/") 89 | print(current_dir_files) 90 | current_offset = 0 91 | current_selected_index = 0 92 | 93 | 94 | def on_button_b_clicked(): 95 | print("on_button_b_clicked") 96 | global current_offset, current_selected_index 97 | current_selected_index += 1 98 | if current_selected_index >= len(current_dir_files): 99 | current_selected_index = 0 100 | if current_selected_index >= 7: 101 | current_offset = current_selected_index - 6 102 | else: 103 | current_offset = 0 104 | print("current_selected=", current_selected_index, 105 | "current_offset=", current_offset) 106 | 107 | 108 | try: 109 | while True: 110 | x_offset = 4 111 | y_offset = 6 112 | lcd.clear() 113 | for i in range(current_offset, len(current_dir_files)): 114 | file_name = current_dir_files[i] 115 | f_stat = os.stat('/sd/' + file_name) 116 | file_readable_size = sizeof_fmt(f_stat[6]) 117 | if S_ISDIR(f_stat[0]): 118 | file_name = file_name + '/' 119 | # print("current i=", i) 120 | is_current = current_selected_index == i 121 | line = "%s %d %s" % ("->" if is_current else " ", i, file_name) 122 | lcd.draw_string(x_offset, y_offset, line, lcd.WHITE, lcd.RED) 123 | lcd.draw_string(lcd.width() - 50, y_offset, 124 | file_readable_size, lcd.WHITE, lcd.BLUE) 125 | y_offset += 18 126 | if y_offset > lcd.height(): 127 | print("y_offset > height(), break") 128 | break 129 | del file_name 130 | while but_b.value() != 0: 131 | # wait b key 132 | pass 133 | while but_b.value() == 0: 134 | # wait b key release 135 | pass 136 | on_button_b_clicked() 137 | 138 | except KeyboardInterrupt: 139 | sys.exit() 140 | -------------------------------------------------------------------------------- /app_camera.py: -------------------------------------------------------------------------------- 1 | import lcd 2 | from framework import BaseApp, NeedRebootException 3 | 4 | import sensor 5 | import KPU as kpu 6 | 7 | 8 | class CameraApp(BaseApp): 9 | def __init__(self, system): 10 | super(CameraApp, self).__init__(system) 11 | self.__initialized = False 12 | 13 | def __lazy_init(self): 14 | err_counter = 0 15 | 16 | while 1: 17 | try: 18 | sensor.reset() # Reset sensor may failed, let's try sometimes 19 | break 20 | except Exception: 21 | err_counter = err_counter + 1 22 | if err_counter == 20: 23 | lcd.draw_string(lcd.width() // 2 - 100, lcd.height() // 2 - 4, 24 | "Error: Sensor Init Failed", lcd.WHITE, lcd.RED) 25 | time.sleep(0.1) 26 | continue 27 | print("progress 1 OK!") 28 | sensor.set_pixformat(sensor.RGB565) 29 | sensor.set_framesize(sensor.QVGA) # QVGA=320x240 30 | sensor.run(1) 31 | 32 | print("progress 2 OK!") 33 | self.task = kpu.load(0x300000) # Load Model File from Flash 34 | anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 35 | 5.3658, 5.155437, 6.92275, 6.718375, 9.01025) 36 | # Anchor data is for bbox, extracted from the training sets. 37 | print("progress 3 OK!") 38 | kpu.init_yolo2(self.task, 0.5, 0.3, 5, anchor) 39 | 40 | self.but_stu = 1 41 | 42 | self.__initialized = True 43 | 44 | def on_back_pressed(self): 45 | raise NeedRebootException() 46 | 47 | def on_draw(self): 48 | if not self.__initialized: 49 | self.__lazy_init() 50 | try: 51 | while True: 52 | img = sensor.snapshot() # Take an image from sensor 53 | print("progress 4 OK!") 54 | # Run the detection routine 55 | bbox = kpu.run_yolo2(self.task, img) 56 | if bbox: 57 | for i in bbox: 58 | print(i) 59 | img.draw_rectangle(i.rect()) 60 | lcd.display(img) 61 | home_button = self.get_system().home_button 62 | # TODO 63 | led_w = self.get_system().led_w 64 | if home_button.value() == 0 and self.but_stu == 1: 65 | if led_w.value() == 1: 66 | led_w.value(0) 67 | else: 68 | led_w.value(1) 69 | self.but_stu = 0 70 | if home_button.value() == 1 and self.but_stu == 0: 71 | self.but_stu = 1 72 | 73 | except KeyboardInterrupt: 74 | a = kpu.deinit(task) 75 | sys.exit() 76 | -------------------------------------------------------------------------------- /app_explorer.py: -------------------------------------------------------------------------------- 1 | from framework import BaseApp 2 | import os 3 | import lcd 4 | 5 | # import uos 6 | 7 | S_IFDIR = 0o040000 # directory 8 | 9 | 10 | # noinspection PyPep8Naming 11 | def S_IFMT(mode): 12 | """Return the portion of the file's mode that describes the 13 | file type. 14 | """ 15 | return mode & 0o170000 16 | 17 | 18 | # noinspection PyPep8Naming 19 | def S_ISDIR(mode): 20 | """Return True if mode is from a directory.""" 21 | return S_IFMT(mode) == S_IFDIR 22 | 23 | 24 | def sizeof_fmt(num, suffix='B'): 25 | for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: 26 | if abs(num) < 1024.0: 27 | return "%3.1f%s%s" % (num, unit, suffix) 28 | num /= 1024.0 29 | return "%.1f%s%s" % (num, 'Yi', suffix) 30 | 31 | 32 | class ExplorerApp(BaseApp): 33 | def __init__(self, system): 34 | super(ExplorerApp, self).__init__(system) 35 | self.current_offset = 0 36 | self.current_selected_index = 0 37 | self.__initialized = False 38 | 39 | def __lazy_init(self): 40 | current_dir_files = os.listdir("/sd/") 41 | self.current_dir_file_info_list = [] 42 | for file_name in current_dir_files: 43 | info = {} 44 | info["file_name"] = file_name 45 | try: 46 | f_stat = os.stat('/sd/' + file_name) 47 | info["stat"] = f_stat 48 | except Exception as e: 49 | info["stat"] = None 50 | print( 51 | "----- error when calling os.stat() ----- file_name =", file_name, e) 52 | self.current_dir_file_info_list.append(info) 53 | print(self.current_dir_file_info_list) 54 | self.__initialized = True 55 | 56 | def on_top_button_changed(self, state): 57 | if state == "pressed": 58 | self.current_selected_index += 1 59 | if self.current_selected_index >= len(self.current_dir_file_info_list): 60 | self.current_selected_index = 0 61 | if self.current_selected_index >= 7: 62 | self.current_offset = self.current_selected_index - 6 63 | else: 64 | self.current_offset = 0 65 | print("current_selected=", self.current_selected_index, 66 | "current_offset=", self.current_offset) 67 | self.invalidate_drawing() 68 | 69 | def on_draw(self): 70 | if not self.__initialized: 71 | self.__lazy_init() 72 | x_offset = 4 73 | y_offset = 6 74 | lcd.clear() 75 | print("progress 0") 76 | for i in range(self.current_offset, len(self.current_dir_file_info_list)): 77 | # gc.collect() 78 | print("progress 1") 79 | file_info = self.current_dir_file_info_list[i] 80 | file_name = file_info["file_name"] 81 | f_stat = file_info["stat"] 82 | if f_stat != None: 83 | if S_ISDIR(f_stat[0]): 84 | file_name = file_name + '/' 85 | file_readable_size = sizeof_fmt(f_stat[6]) 86 | lcd.draw_string(lcd.width() - 50, y_offset, 87 | file_readable_size, lcd.WHITE, lcd.BLUE) 88 | is_current = self.current_selected_index == i 89 | line = "%s %d %s" % ("->" if is_current else " ", i, file_name) 90 | lcd.draw_string(x_offset, y_offset, line, lcd.WHITE, lcd.RED) 91 | # gc.collect() 92 | print("progress 2") 93 | y_offset += 18 94 | if y_offset > lcd.height(): 95 | print("y_offset > height(), break") 96 | break 97 | print("progress 3") 98 | -------------------------------------------------------------------------------- /app_launcher.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import lcd 4 | import sys 5 | import time 6 | import machine 7 | 8 | import image 9 | 10 | from framework import BaseApp 11 | from app_camera import CameraApp 12 | from app_explorer import ExplorerApp 13 | from app_system_info import SystemInfoApp 14 | 15 | import config 16 | import resource 17 | 18 | 19 | class LauncherApp(BaseApp): 20 | def __init__(self, system): 21 | super(LauncherApp, self).__init__(system) 22 | print("LauncherApp: super.__init__() called") 23 | self.app_list = resource.app_list 24 | self.battery_icon_list = resource.battery_icon_list 25 | self.battery_charging_icon_list = resource.battery_charging_icon_list 26 | self.arrow_icon_path = resource.arrow_icon_path 27 | self.app_count = len(self.app_list) 28 | self.cursor_index = 0 29 | self.need_show_top_button_tip = True 30 | self.animation_count = 3 31 | self.pending_animation_values = [] 32 | self.icon_path_to_image_cache = {} 33 | self.app_periodic_task_last_time = 0 34 | 35 | def draw_icon(self, screen_canvas, icon_path, x, y, horizontal_align, vertical_align): 36 | """closure: an inner function inside a method""" 37 | try: 38 | icon = None 39 | if icon_path in self.icon_path_to_image_cache: 40 | icon = self.icon_path_to_image_cache[icon_path] 41 | else: 42 | icon = image.Image(icon_path) 43 | self.icon_path_to_image_cache[icon_path] = icon 44 | # calculate horizontal 45 | if "center" == horizontal_align: 46 | left = x - icon.width() // 2 47 | elif "right" == horizontal_align: 48 | left = x - icon.width() 49 | # calculate vertical 50 | if "center" == vertical_align: 51 | top = y - icon.height() // 2 52 | elif "top" == vertical_align: 53 | top = y 54 | screen_canvas.draw_image(icon, left, top) 55 | except Exception as e: 56 | print("cannot draw icon:", e) 57 | sys.print_exception(e) 58 | 59 | def on_draw(self): 60 | print("LauncherApp.on_draw()") 61 | icon_width = 64 62 | icon_height = 60 63 | icon_padding = 6 64 | icon_margin_top = 0 65 | screen_canvas = image.Image() 66 | vbat = self.get_system().pmu.getVbatVoltage() / 1000.0 67 | usb_plugged = self.get_system().pmu.is_usb_plugged_in() 68 | battery_level = self.calculate_battery_level(vbat) 69 | vbat_str = str(vbat) + "V" 70 | print("vbat", vbat_str) 71 | # screen_canvas.draw_string(180, 10, vbat_str, lcd.GREEN, scale=1) 72 | 73 | icons_count = screen_canvas.width() // (icon_width + icon_padding) 74 | if icons_count % 2 == 0: 75 | icons_count += 1 76 | else: 77 | icons_count += 2 78 | # icons_count must be an odd integer 79 | icons_half_count = icons_count // 2 80 | animation_offset = 0 81 | # handle animation 82 | if len(self.pending_animation_values) > 0: 83 | anim_index = self.pending_animation_values.pop() 84 | animation_offset = int( 85 | (icon_width + icon_padding * 2) * anim_index / self.animation_count) 86 | # invalidate when need animation 87 | self.invalidate_drawing() 88 | for i in range(-icons_half_count, icons_half_count + 1): 89 | icon_center_x = screen_canvas.width() // 2 + i * (icon_width + icon_padding) 90 | icon_center_y = screen_canvas.height() // 2 + icon_margin_top 91 | icon_center_x += animation_offset 92 | index = (self.cursor_index + i) % self.app_count 93 | self.draw_icon(screen_canvas, self.app_list[index]["icon"], 94 | icon_center_x, icon_center_y, "center", "center") 95 | # draw center small arrow icon below 96 | self.draw_icon(screen_canvas, self.arrow_icon_path, screen_canvas.width() // 2, 97 | screen_canvas.height() // 2 + icon_height // 2 + icon_padding + icon_margin_top, "center", "top") 98 | print("draw arrow ok") 99 | battery_percent = battery_level * 100.0 100 | battery_icon = self.find_battery_icon(battery_percent, usb_plugged) 101 | print("before draw battery") 102 | battery_icon_padding = 3 103 | self.draw_icon(screen_canvas, battery_icon, 104 | screen_canvas.width() - battery_icon_padding, battery_icon_padding, "right", "top") 105 | print("after draw battery") 106 | lcd.display(screen_canvas) 107 | del screen_canvas 108 | lcd.draw_string(3, 3, "Battery: %.3fV %.1f%%" % 109 | (vbat, battery_percent), lcd.GREEN) 110 | print("launcher on_draw end") 111 | 112 | def navigate(self, app): 113 | self.get_system().navigate(app) 114 | print("navigate from", self, "to", app) 115 | 116 | def on_home_button_changed(self, state): 117 | # avoid navigate twice here 118 | if state == "pressed": 119 | app_id = self.app_list[self.cursor_index]["id"] 120 | if app_id == "camera": 121 | self.navigate(CameraApp(self.get_system())) 122 | elif app_id == "explorer": 123 | self.navigate(ExplorerApp(self.get_system())) 124 | elif app_id == "reboot": 125 | machine.reset() 126 | elif app_id == "power": 127 | self.get_system().pmu.setEnterSleepMode() 128 | elif app_id == "system_info": 129 | self.navigate(SystemInfoApp(self.get_system())) 130 | elif app_id == "brightness": 131 | self.change_brightness() 132 | return True 133 | 134 | def change_brightness(self): 135 | value = config.get_brightness() 136 | value += 1 137 | if value > 15: 138 | value = 7 139 | self.get_system().pmu.setScreenBrightness(value) 140 | config.save_config("brightness", value) 141 | print("set and save brightness value to", value) 142 | 143 | def on_top_button_changed(self, state): 144 | if state == "pressed": 145 | self.cursor_index += 1 146 | print(self.cursor_index, len(self.app_list)) 147 | if self.cursor_index >= self.app_count: 148 | self.cursor_index = 0 149 | self.generate_pending_animations() 150 | self.invalidate_drawing() 151 | print(self.cursor_index, len(self.app_list)) 152 | return True 153 | 154 | def generate_pending_animations(self): 155 | self.pending_animation_values = list(range(self.animation_count)) 156 | 157 | def on_back_pressed(self): 158 | # handled by launcher app 159 | # TODO show power options 160 | self.cursor_index = 0 161 | self.invalidate_drawing() 162 | return True 163 | 164 | def app_periodic_task(self): 165 | now_ticks_ms = time.ticks_ms() 166 | if now_ticks_ms - self.app_periodic_task_last_time > 2000: 167 | self.app_periodic_task_last_time = now_ticks_ms 168 | self.invalidate_drawing() 169 | 170 | def find_battery_icon(self, battery_percent, is_charging): 171 | icon_list = self.battery_charging_icon_list if is_charging else self.battery_icon_list 172 | index = int(battery_percent / 20.0) 173 | return icon_list[index] 174 | 175 | def calculate_battery_level(self, vbat): 176 | levels = [4.13, 4.06, 3.98, 3.92, 3.87, 177 | 3.82, 3.79, 3.77, 3.74, 3.68, 3.45, 3.00] 178 | level = 1.0 179 | if vbat >= levels[0]: 180 | level = 1.0 181 | elif vbat >= levels[1]: 182 | level = 0.9 183 | level += 0.1 * (vbat - levels[1]) / (levels[0] - levels[1]) 184 | elif vbat >= levels[2]: 185 | level = 0.8 186 | level += 0.1 * (vbat - levels[2]) / (levels[1] - levels[2]) 187 | elif vbat >= levels[3]: 188 | level = 0.7 189 | level += 0.1 * (vbat - levels[3]) / (levels[2] - levels[3]) 190 | elif vbat >= levels[4]: 191 | level = 0.6 192 | level += 0.1 * (vbat - levels[4]) / (levels[3] - levels[4]) 193 | elif vbat >= levels[5]: 194 | level = 0.5 195 | level += 0.1 * (vbat - levels[5]) / (levels[4] - levels[5]) 196 | elif vbat >= levels[6]: 197 | level = 0.4 198 | level += 0.1 * (vbat - levels[6]) / (levels[5] - levels[6]) 199 | elif vbat >= levels[7]: 200 | level = 0.3 201 | level += 0.1 * (vbat - levels[7]) / (levels[6] - levels[7]) 202 | elif vbat >= levels[8]: 203 | level = 0.2 204 | level += 0.1 * (vbat - levels[8]) / (levels[7] - levels[8]) 205 | elif vbat >= levels[9]: 206 | level = 0.1 207 | level += 0.1 * (vbat - levels[9]) / (levels[8] - levels[9]) 208 | elif vbat >= levels[10]: 209 | level = 0.05 210 | level += 0.05 * (vbat - levels[10]) / (levels[9] - levels[10]) 211 | elif vbat >= levels[11]: 212 | level = 0.0 213 | level += 0.05 * (vbat - levels[11]) / (levels[10] - levels[11]) 214 | else: 215 | level = 0.0 216 | return level 217 | -------------------------------------------------------------------------------- /app_system_info.py: -------------------------------------------------------------------------------- 1 | from framework import BaseApp 2 | import os 3 | import lcd 4 | import machine 5 | import ubinascii 6 | 7 | 8 | def sizeof_fmt(num, suffix='B'): 9 | for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: 10 | if abs(num) < 1024.0: 11 | return "%3.1f%s%s" % (num, unit, suffix) 12 | num /= 1024.0 13 | return "%.1f%s%s" % (num, 'Yi', suffix) 14 | 15 | 16 | class SystemInfoApp(BaseApp): 17 | def __init__(self, system): 18 | super(SystemInfoApp, self).__init__(system) 19 | self.__initialized = False 20 | 21 | def __lazy_init(self): 22 | self.system_uname = os.uname() 23 | self.device_id = ubinascii.hexlify(machine.unique_id()).decode() 24 | root_files = os.listdir('/') 25 | self.fs_info_list = [] 26 | for f in root_files: 27 | fs_path = '/' + f 28 | fs_stat = os.statvfs(fs_path) 29 | bs1 = fs_stat[0] 30 | bs2 = fs_stat[1] 31 | total_blocks = fs_stat[2] 32 | free_blocks = fs_stat[3] 33 | info = "%s total=%s free=%s" % ( 34 | fs_path, 35 | sizeof_fmt(bs1 * total_blocks), 36 | sizeof_fmt(bs2 * free_blocks) 37 | ) 38 | self.fs_info_list.append(info) 39 | print(info) 40 | self.__initialized = True 41 | 42 | def on_top_button_changed(self, state): 43 | pass 44 | 45 | def on_draw(self): 46 | if not self.__initialized: 47 | self.__lazy_init() 48 | lcd.clear() 49 | y = 3 50 | lcd.draw_string(3, 3, self.system_uname.machine, lcd.WHITE, lcd.BLUE) 51 | y += 16 52 | lcd.draw_string(3, y, self.system_uname.version, 53 | lcd.WHITE, lcd.BLUE) 54 | y += 16 55 | lcd.draw_string(3, y, self.device_id, lcd.WHITE, lcd.BLUE) 56 | for info in self.fs_info_list: 57 | y += 16 58 | lcd.draw_string(3, y, info, lcd.WHITE, lcd.BLUE) 59 | -------------------------------------------------------------------------------- /boot.py: -------------------------------------------------------------------------------- 1 | # import gc 2 | # gc.disable() 3 | 4 | from m5stickv_system import M5StickVSystem 5 | 6 | '''This program suppports MaixPy firmware''' 7 | 8 | m5stickv_system = M5StickVSystem() 9 | m5stickv_system.run() 10 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import ujson 2 | import sys 3 | 4 | config_path = "/sd/config.json" 5 | 6 | normal_brightness = 8 7 | 8 | config_cache = None 9 | 10 | 11 | def get_config(): 12 | global config_cache 13 | if config_cache is None: 14 | conf = {} 15 | try: 16 | f = open(config_path, "rb") 17 | conf = ujson.load(f) 18 | f.close() 19 | except OSError: 20 | print("config file not exist, use default dict") 21 | except ValueError: 22 | print("invalid config file format, use default dict") 23 | if type(conf) is not dict: 24 | conf = {} 25 | config_cache = conf 26 | return config_cache 27 | 28 | 29 | def save_config(key, value): 30 | config = get_config() 31 | config[key] = value 32 | save_config_to_file(config) 33 | 34 | 35 | def save_config_to_file(config): 36 | try: 37 | f = open(config_path, "wb") 38 | ujson.dump(config, f) 39 | f.close() 40 | except OSError as e: 41 | sys.print_exception(e) 42 | print("save_config_to_file end") 43 | 44 | 45 | def get_config_by_key(key): 46 | config = get_config() 47 | if key in config: 48 | return config[key] 49 | else: 50 | return None 51 | 52 | 53 | def get_brightness(): 54 | brightness_conf = get_config_by_key("brightness") 55 | return brightness_conf if brightness_conf is not None else normal_brightness 56 | -------------------------------------------------------------------------------- /docs/m5stickv_computer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/docs/m5stickv_computer.png -------------------------------------------------------------------------------- /framework.py: -------------------------------------------------------------------------------- 1 | class NeedRebootException(Exception): 2 | pass 3 | 4 | 5 | class BaseApp: 6 | def __init__(self, system): 7 | print("BaseApp.__init__ called") 8 | self.system = system 9 | 10 | def on_draw(self): 11 | pass 12 | 13 | def on_back_pressed(self): 14 | # not handled by default 15 | return False 16 | 17 | def on_home_button_changed(self, state): 18 | return False 19 | 20 | def on_top_button_changed(self, state): 21 | return False 22 | 23 | def invalidate_drawing(self): 24 | self.system.invalidate_drawing() 25 | 26 | def get_system(self): 27 | return self.system 28 | 29 | def app_periodic_task(self): 30 | pass 31 | -------------------------------------------------------------------------------- /m5stickv_system.py: -------------------------------------------------------------------------------- 1 | import lcd 2 | import machine 3 | import sys 4 | 5 | from Maix import GPIO 6 | from board import board_info 7 | from fpioa_manager import fm 8 | 9 | import image 10 | # import gc 11 | import time 12 | import resource 13 | import config 14 | 15 | from my_pmu import AXP192 16 | from app_launcher import LauncherApp 17 | from framework import NeedRebootException 18 | 19 | 20 | class M5StickVSystem: 21 | def __init__(self): 22 | self.pmu = AXP192() 23 | self.pmu.setScreenBrightness(0) 24 | self.pmu.set_on_pressed_listener(self.on_pek_button_pressed) 25 | self.pmu.set_on_long_pressed_listener(self.on_pek_button_long_pressed) 26 | self.pmu.set_system_periodic_task(self.system_periodic_task) 27 | self.app_stack = [] 28 | 29 | lcd.init() 30 | self.pmu.setScreenBrightness(0) 31 | lcd.rotation(2) # Rotate the lcd 180deg 32 | # set brightness to zero before first draw to avoid flower screen 33 | self.pmu.setScreenBrightness(0) 34 | 35 | self.home_button = None 36 | self.top_button = None 37 | self.led_w = None 38 | self.led_r = None 39 | self.led_g = None 40 | self.led_b = None 41 | self.spk_sd = None 42 | self.is_handling_irq = False 43 | self.init_fm() 44 | 45 | self.is_drawing_dirty = False 46 | self.is_boot_complete_first_draw = True 47 | self.show_provision() 48 | self.navigate(LauncherApp(self)) 49 | 50 | def show_provision(self): 51 | img = image.Image(resource.provision_image_path) 52 | lcd.display(img) 53 | del img 54 | lcd.draw_string(54, 6, 55 | "NEXT", lcd.RED, lcd.BLACK) 56 | lcd.draw_string(168, 6, 57 | "ENTER", lcd.RED, lcd.BLACK) 58 | lcd.draw_string(152, lcd.height() - 18, 59 | "BACK/POWER", lcd.RED, lcd.BLACK) 60 | lcd.draw_string(21, lcd.height() - 18, 61 | "StickV Computer", lcd.WHITE, lcd.BLACK) 62 | self.check_restore_brightness() 63 | self.wait_event() 64 | 65 | def button_irq(self, gpio, optional_pin_num=None): 66 | print("button_irq start:", gpio, optional_pin_num) 67 | # Notice: optional_pin_num exist in older firmware 68 | if self.is_handling_irq: 69 | print("is_handing_irq, ignore") 70 | return 71 | self.is_handing_irq = True 72 | value = gpio.value() 73 | state = "released" if value else "pressed" 74 | # msg = {"type": "key_event", "gpio": gpio, "state": state} 75 | print("button_irq:", gpio, optional_pin_num, state) 76 | if self.home_button is gpio: 77 | self.on_home_button_changed(state) 78 | elif self.top_button is gpio: 79 | self.on_top_button_changed(state) 80 | self.is_handing_irq = False 81 | print("button_irq end:", gpio, optional_pin_num, state) 82 | #gpio.irq(self.button_irq, GPIO.IRQ_BOTH, GPIO.WAKEUP_NOT_SUPPORT, 7) 83 | 84 | # noinspection SpellCheckingInspection 85 | def init_fm(self): 86 | # home button 87 | fm.register(board_info.BUTTON_A, fm.fpioa.GPIOHS21) 88 | # PULL_UP is required here! 89 | self.home_button = GPIO(GPIO.GPIOHS21, GPIO.IN, GPIO.PULL_UP) 90 | # self.home_button.irq(self.button_irq, GPIO.IRQ_BOTH, 91 | # GPIO.WAKEUP_NOT_SUPPORT, 7) 92 | 93 | if self.home_button.value() == 0: # If don't want to run the demo 94 | sys.exit() 95 | 96 | # top button 97 | fm.register(board_info.BUTTON_B, fm.fpioa.GPIOHS22) 98 | # PULL_UP is required here! 99 | self.top_button = GPIO(GPIO.GPIOHS22, GPIO.IN, GPIO.PULL_UP) 100 | # self.top_button.irq(self.button_irq, GPIO.IRQ_BOTH, 101 | # GPIO.WAKEUP_NOT_SUPPORT, 7) 102 | return # TODO: fix me 103 | fm.register(board_info.LED_W, fm.fpioa.GPIO3) 104 | self.led_w = GPIO(GPIO.GPIO3, GPIO.OUT) 105 | self.led_w.value(1) # RGBW LEDs are Active Low 106 | 107 | fm.register(board_info.LED_R, fm.fpioa.GPIO4) 108 | self.led_r = GPIO(GPIO.GPIO4, GPIO.OUT) 109 | self.led_r.value(1) # RGBW LEDs are Active Low 110 | 111 | fm.register(board_info.LED_G, fm.fpioa.GPIO5) 112 | self.led_g = GPIO(GPIO.GPIO5, GPIO.OUT) 113 | self.led_g.value(1) # RGBW LEDs are Active Low 114 | 115 | fm.register(board_info.LED_B, fm.fpioa.GPIO6) 116 | self.led_b = GPIO(GPIO.GPIO6, GPIO.OUT) 117 | self.led_b.value(1) # RGBW LEDs are Active Low 118 | 119 | fm.register(board_info.SPK_SD, fm.fpioa.GPIO0) 120 | self.spk_sd = GPIO(GPIO.GPIO0, GPIO.OUT) 121 | self.spk_sd.value(1) # Enable the SPK output 122 | 123 | fm.register(board_info.SPK_DIN, fm.fpioa.I2S0_OUT_D1) 124 | fm.register(board_info.SPK_BCLK, fm.fpioa.I2S0_SCLK) 125 | fm.register(board_info.SPK_LRCLK, fm.fpioa.I2S0_WS) 126 | 127 | def invalidate_drawing(self): 128 | print("invalidate_drawing") 129 | self.is_drawing_dirty = True 130 | 131 | def run(self): 132 | try: 133 | self.run_inner() 134 | except Exception as e: 135 | import uio 136 | string_io = uio.StringIO() 137 | sys.print_exception(e, string_io) 138 | s = string_io.getvalue() 139 | print("showing blue screen:", s) 140 | lcd.clear(lcd.BLUE) 141 | msg = "** " + str(e) 142 | chunks, chunk_size = len(msg), 29 143 | msg_lines = [msg[i:i+chunk_size] 144 | for i in range(0, chunks, chunk_size)] 145 | # "A problem has been detected and windows has been shut down to prevent damange to your m5stickv :)" 146 | lcd.draw_string( 147 | 1, 1, "A problem has been detected and windows", lcd.WHITE, lcd.BLUE) 148 | lcd.draw_string( 149 | 1, 1 + 5 + 16, "Technical information:", lcd.WHITE, lcd.BLUE) 150 | current_y = 1 + 5 + 16 * 2 151 | for line in msg_lines: 152 | lcd.draw_string(1, current_y, line, lcd.WHITE, lcd.BLUE) 153 | current_y += 16 154 | if current_y >= lcd.height(): 155 | break 156 | lcd.draw_string(1, current_y, s, lcd.WHITE, lcd.BLUE) 157 | lcd.draw_string( 158 | 1, lcd.height() - 17, "Will reboot after 10 seconds..", lcd.WHITE, lcd.BLUE) 159 | time.sleep(10) 160 | machine.reset() 161 | 162 | def wait_event(self): 163 | """key event or view invalidate event""" 164 | print("wait for all key release") 165 | while self.home_button.value() == 0 or self.top_button.value() == 0: 166 | pass 167 | print("key released, now wait for a event") 168 | while self.home_button.value() == 1 and self.top_button.value() == 1 and not self.is_drawing_dirty: 169 | pass 170 | print("some event arrived") 171 | if self.is_drawing_dirty: 172 | print("drawing dirty event") 173 | return ("drawing", "dirty") 174 | elif self.home_button.value() == 0: 175 | print("home_button pressed") 176 | return (self.home_button, "pressed") 177 | self.on_home_button_changed("pressed") 178 | elif self.top_button.value() == 0: 179 | print("top_button pressed") 180 | return (self.top_button, "pressed") 181 | self.on_top_button_changed("pressed") 182 | else: 183 | return None 184 | 185 | def check_restore_brightness(self): 186 | if self.is_boot_complete_first_draw: 187 | self.is_boot_complete_first_draw = False 188 | self.pmu.setScreenBrightness( 189 | config.get_brightness()) # 7-15 is ok, normally 8 190 | 191 | def run_inner(self): 192 | while True: 193 | if self.is_drawing_dirty: 194 | print("drawing is dirty") 195 | self.is_drawing_dirty = False 196 | current_app = self.get_current_app() 197 | # print("before on_draw() of", current_app, "free memory:", gc.mem_free()) 198 | print("current_app.on_draw() start") 199 | current_app.on_draw() 200 | print("current_app.on_draw() end") 201 | # print("on_draw() of", current_app, "called, free memory:", gc.mem_free()) 202 | # this gc is to avoid: "core dump: misaligned load" error 203 | # print("after gc.collect(), free memory:", gc.mem_free()) 204 | self.check_restore_brightness() 205 | # print("sleep_ms for 1ms") 206 | # time.sleep_ms(1) 207 | # print("sleep_ms for 1ms end") 208 | else: 209 | event_info = self.wait_event() 210 | if event_info is not None and len(event_info) == 2: 211 | event = event_info[0] 212 | state = event_info[1] 213 | if event == self.home_button: 214 | self.on_home_button_changed(state) 215 | elif event == self.top_button: 216 | self.on_top_button_changed(state) 217 | 218 | def navigate(self, app): 219 | self.app_stack.append(app) 220 | self.invalidate_drawing() 221 | 222 | def navigate_back(self): 223 | if len(self.app_stack) > 0: 224 | self.app_stack.pop() 225 | self.invalidate_drawing() 226 | 227 | def get_current_app(self): 228 | return self.app_stack[-1] if len(self.app_stack) > 0 else None 229 | 230 | def on_pek_button_pressed(self, axp): 231 | # treat short press as navigate back 232 | print("on_pek_button_pressed", axp) 233 | handled = False 234 | current_app = self.get_current_app() 235 | if current_app: 236 | try: 237 | handled = current_app.on_back_pressed() 238 | except NeedRebootException: 239 | machine.reset() 240 | if not handled: 241 | print("on_back_pressed() not handled, exit current app") 242 | self.navigate_back() 243 | 244 | def system_periodic_task(self, axp): 245 | current = self.get_current_app() 246 | if current: 247 | current.app_periodic_task() 248 | 249 | # noinspection PyMethodMayBeStatic 250 | def on_pek_button_long_pressed(self, axp): 251 | print("on_pek_button_long_pressed", axp) 252 | axp.setEnterSleepMode() 253 | 254 | def on_home_button_changed(self, state): 255 | print("on_home_button_changed", state) 256 | self.get_current_app().on_home_button_changed(state) 257 | 258 | def on_top_button_changed(self, state): 259 | print("on_top_button_changed", state) 260 | self.get_current_app().on_top_button_changed(state) 261 | -------------------------------------------------------------------------------- /my_pmu.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # start of pmu.py 4 | from machine import I2C, Timer 5 | 6 | 7 | class PMUError(Exception): 8 | pass 9 | 10 | 11 | class NotFoundError(PMUError): 12 | pass 13 | 14 | 15 | class OutOfRange(PMUError): 16 | pass 17 | 18 | 19 | class AXP192: 20 | def __init__(self, i2c_dev=None): 21 | if i2c_dev is None: 22 | try: 23 | self.i2cDev = I2C(I2C.I2C0, freq=400000, scl=28, sda=29) 24 | except Exception: 25 | raise PMUError("Unable to init I2C0 as Master") 26 | else: 27 | self.i2cDev = i2c_dev 28 | 29 | self.axp192Addr = 52 30 | 31 | self.__preButPressed__ = -1 32 | self.onPressedListener = None 33 | self.onLongPressedListener = None 34 | self.system_periodic_task = None 35 | scan_list = self.i2cDev.scan() 36 | if self.axp192Addr not in scan_list: 37 | raise NotFoundError 38 | # enable timer by default 39 | self.enablePMICSleepMode(True) 40 | 41 | def set_on_pressed_listener(self, listener): 42 | self.onPressedListener = listener 43 | 44 | def set_on_long_pressed_listener(self, listener): 45 | self.onLongPressedListener = listener 46 | 47 | def set_system_periodic_task(self, task): 48 | self.system_periodic_task = task 49 | 50 | def __chkPwrKeyWaitForSleep__(self, timer): 51 | if self.system_periodic_task: 52 | self.system_periodic_task(self) 53 | self.i2cDev.writeto(52, bytes([0x46])) 54 | pek_stu = (self.i2cDev.readfrom(52, 1))[0] 55 | self.i2cDev.writeto_mem(52, 0x46, 0xFF, mem_size=8) # Clear IRQ 56 | 57 | # Prevent loop in restart, wait for release 58 | if self.__preButPressed__ == -1 and ((pek_stu & (0x01 << 1)) or (pek_stu & 0x01)): 59 | # print("return") 60 | return 61 | 62 | if self.__preButPressed__ == -1 and ((pek_stu & (0x01 << 1)) == False and (pek_stu & 0x01) == False): 63 | self.__preButPressed__ = 0 64 | print("self.__preButPressed__ == 0") 65 | 66 | if pek_stu & 0x01: 67 | print("before enter sleep") 68 | if self.onLongPressedListener: 69 | self.onLongPressedListener(self) 70 | print("after enter sleep is never called") 71 | 72 | if pek_stu & (0x01 << 1): 73 | if self.onPressedListener: 74 | self.onPressedListener(self) 75 | 76 | def __write_reg(self, reg_address, value): 77 | self.i2cDev.writeto_mem( 78 | self.axp192Addr, reg_address, value, mem_size=8) 79 | 80 | def __read_reg(self, reg_address): 81 | self.i2cDev.writeto(self.axp192Addr, bytes([reg_address])) 82 | return (self.i2cDev.readfrom(self.axp192Addr, 1))[0] 83 | 84 | def __is_bit_set(self, byte_data, bit_index): 85 | return byte_data & (1 << bit_index) != 0 86 | 87 | def enable_adc(self, enable): 88 | if enable: 89 | self.__write_reg(0x82, 0xFF) 90 | else: 91 | self.__write_reg(0x82, 0x00) 92 | 93 | def enable_coulomb_counter(self, enable): 94 | if enable: 95 | self.__write_reg(0xB8, 0x80) 96 | else: 97 | self.__write_reg(0xB8, 0x00) 98 | 99 | def stop_coulomb_counter(self): 100 | self.__write_reg(0xB8, 0xC0) 101 | 102 | def clear_coulomb_counter(self): 103 | self.__write_reg(0xB8, 0xA0) 104 | 105 | def __get_coulomb_charge_data(self): 106 | CoulombCounter_LSB = self.__read_reg(0xB0) 107 | CoulombCounter_B1 = self.__read_reg(0xB1) 108 | CoulombCounter_B2 = self.__read_reg(0xB2) 109 | CoulombCounter_MSB = self.__read_reg(0xB3) 110 | 111 | return ((CoulombCounter_LSB << 24) + (CoulombCounter_B1 << 16) + 112 | (CoulombCounter_B2 << 8) + CoulombCounter_MSB) 113 | 114 | def __get_coulomb_discharge_data(self): 115 | CoulombCounter_LSB = self.__read_reg(0xB4) 116 | CoulombCounter_B1 = self.__read_reg(0xB5) 117 | CoulombCounter_B2 = self.__read_reg(0xB6) 118 | CoulombCounter_MSB = self.__read_reg(0xB7) 119 | 120 | return ((CoulombCounter_LSB << 24) + (CoulombCounter_B1 << 16) + 121 | (CoulombCounter_B2 << 8) + CoulombCounter_MSB) 122 | 123 | def get_coulomb_counter_data(self): 124 | return 65536 * 0.5 * (self.__get_coulomb_charge_data() - 125 | self.__get_coulomb_discharge_data) / 3600.0 / 25.0 126 | 127 | def getVbatVoltage(self): 128 | Vbat_LSB = self.__read_reg(0x78) 129 | Vbat_MSB = self.__read_reg(0x79) 130 | 131 | return ((Vbat_LSB << 4) + Vbat_MSB) * 1.1 # AXP192-DS PG26 1.1mV/div 132 | 133 | def is_usb_plugged_in(self): 134 | power_data = self.__read_reg(0x00) 135 | return self.__is_bit_set(power_data, 6) and self.__is_bit_set(power_data, 7) 136 | 137 | def getUSBVoltage(self): 138 | Vin_LSB = self.__read_reg(0x56) 139 | Vin_MSB = self.__read_reg(0x57) 140 | 141 | return ((Vin_LSB << 4) + Vin_MSB) * 1.7 # AXP192-DS PG26 1.7mV/div 142 | 143 | def getUSBInputCurrent(self): 144 | Iin_LSB = self.__read_reg(0x58) 145 | Iin_MSB = self.__read_reg(0x59) 146 | 147 | return ((Iin_LSB << 4) + Iin_MSB) * 0.625 # AXP192-DS PG26 0.625mA/div 148 | 149 | def getConnextVoltage(self): 150 | Vcnx_LSB = self.__read_reg(0x5A) 151 | Vcnx_MSB = self.__read_reg(0x5B) 152 | 153 | return ((Vcnx_LSB << 4) + Vcnx_MSB) * 1.7 # AXP192-DS PG26 1.7mV/div 154 | 155 | def getConnextInputCurrent(self): 156 | IinCnx_LSB = self.__read_reg(0x5C) 157 | IinCnx_MSB = self.__read_reg(0x5D) 158 | 159 | # AXP192-DS PG26 0.625mA/div 160 | return ((IinCnx_LSB << 4) + IinCnx_MSB) * 0.625 161 | 162 | def getBatteryChargeCurrent(self): 163 | Ichg_LSB = self.__read_reg(0x7A) 164 | Ichg_MSB = self.__read_reg(0x7B) 165 | 166 | return ((Ichg_LSB << 5) + Ichg_MSB) * 0.5 # AXP192-DS PG27 0.5mA/div 167 | 168 | def getBatteryDischargeCurrent(self): 169 | Idcg_LSB = self.__read_reg(0x7C) 170 | Idcg_MSB = self.__read_reg(0x7D) 171 | 172 | return ((Idcg_LSB << 5) + Idcg_MSB) * 0.5 # AXP192-DS PG27 0.5mA/div 173 | 174 | def getBatteryInstantWatts(self): 175 | Iinswat_LSB = self.__read_reg(0x70) 176 | Iinswat_B2 = self.__read_reg(0x71) 177 | Iinswat_MSB = self.__read_reg(0x72) 178 | 179 | # AXP192-DS PG32 0.5mA*1.1mV/1000/mW 180 | return ((Iinswat_LSB << 16) + (Iinswat_B2 << 8) + Iinswat_MSB) * 1.1 * 0.5 / 1000 181 | 182 | def getTemperature(self): 183 | Temp_LSB = self.__read_reg(0x5E) 184 | Temp_MSB = self.__read_reg(0x5F) 185 | 186 | # AXP192-DS PG26 0.1degC/div -144.7degC Biased 187 | return (((Temp_LSB << 4) + Temp_MSB) * 0.1) - 144.7 188 | 189 | def setK210Vcore(self, vol): 190 | if vol > 1.05 or vol < 0.8: 191 | raise OutOfRange("Voltage is invaild for K210") 192 | DCDC2Steps = int((vol - 0.7) * 1000 / 25) 193 | self.__write_reg(0x23, DCDC2Steps) 194 | 195 | def setScreenBrightness(self, brightness): 196 | if brightness > 15 or brightness < 0: 197 | raise OutOfRange("Range for brightness is from 0 to 15, but min 7 is the screen visible value") 198 | self.__write_reg(0x91, (int(brightness) & 0x0f) << 4) 199 | 200 | def getKeyStatus(self): # -1: NoPress, 1: ShortPress, 2:LongPress 201 | but_stu = self.__read_reg(0x46) 202 | if (but_stu & (0x1 << 1)): 203 | return 1 204 | else: 205 | if (but_stu & (0x1 << 0)): 206 | return 2 207 | else: 208 | return -1 209 | 210 | def setEnterSleepMode(self): 211 | self.__write_reg(0x31, 0x0F) # Enable Sleep Mode 212 | self.__write_reg(0x91, 0x00) # Turn off GPIO0/LDO0 213 | self.__write_reg(0x12, 0x00) # Turn off other power source 214 | 215 | def enablePMICSleepMode(self, enable): 216 | if enable: 217 | # self.__write_reg(0x36, 0x27) # Turnoff PEK Overtime Shutdown 218 | self.__write_reg(0x46, 0xFF) # Clear the interrupts 219 | self.butChkTimer = Timer(Timer.TIMER2, Timer.CHANNEL0, mode=Timer.MODE_PERIODIC, 220 | period=500, callback=self.__chkPwrKeyWaitForSleep__) 221 | else: 222 | # self.__write_reg(0x36, 0x6C) # Set to default 223 | try: 224 | self.butChkTimer.stop() 225 | del self.butChkTimer 226 | except Exception: 227 | pass 228 | 229 | 230 | # end of pmu.py 231 | -------------------------------------------------------------------------------- /others/alert_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/others/alert_64x60.jpg -------------------------------------------------------------------------------- /others/explorer_standalone.py: -------------------------------------------------------------------------------- 1 | import os 2 | import lcd 3 | 4 | 5 | from Maix import GPIO 6 | from board import board_info 7 | from fpioa_manager import fm 8 | 9 | # import uos 10 | 11 | S_IFDIR = 0o040000 # directory 12 | 13 | 14 | # noinspection PyPep8Naming 15 | def S_IFMT(mode): 16 | """Return the portion of the file's mode that describes the 17 | file type. 18 | """ 19 | return mode & 0o170000 20 | 21 | 22 | # noinspection PyPep8Naming 23 | def S_ISDIR(mode): 24 | """Return True if mode is from a directory.""" 25 | return S_IFMT(mode) == S_IFDIR 26 | 27 | 28 | def sizeof_fmt(num, suffix='B'): 29 | for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: 30 | if abs(num) < 1024.0: 31 | return "%3.1f%s%s" % (num, unit, suffix) 32 | num /= 1024.0 33 | return "%.1f%s%s" % (num, 'Yi', suffix) 34 | 35 | 36 | 37 | class ExplorerApp: 38 | def __init__(self): 39 | self.current_offset = 0 40 | self.current_selected_index = 0 41 | self.__initialized = False 42 | self.is_dirty = True; 43 | def __lazy_init(self): 44 | self.current_dir_files = os.listdir("/sd/") 45 | print(self.current_dir_files) 46 | self.__initialized = True 47 | def on_top_button_changed(self, state): 48 | if state == "pressed": 49 | print("pressed") 50 | self.current_selected_index += 1 51 | if self.current_selected_index >= len(self.current_dir_files): 52 | self.current_selected_index = 0 53 | if self.current_selected_index >= 7: 54 | self.current_offset = self.current_selected_index - 6 55 | else: 56 | self.current_offset = 0 57 | print("current_selected=", self.current_selected_index, 58 | "current_offset=", self.current_offset) 59 | self.is_dirty = True 60 | def on_draw(self): 61 | self.is_dirty = False 62 | if not self.__initialized: 63 | self.__lazy_init() 64 | x_offset = 4 65 | y_offset = 6 66 | lcd.clear() 67 | for i in range(self.current_offset, len(self.current_dir_files)): 68 | # gc.collect() 69 | file_name = self.current_dir_files[i] 70 | print(file_name) 71 | try: 72 | f_stat = os.stat('/sd/' + file_name) 73 | if S_ISDIR(f_stat[0]): 74 | file_name = file_name + '/' 75 | # gc.collect() 76 | file_readable_size = sizeof_fmt(f_stat[6]) 77 | lcd.draw_string(lcd.width() - 50, y_offset, 78 | file_readable_size, lcd.WHITE, lcd.BLUE) 79 | except Exception as e: 80 | print("-------------------->", e) 81 | is_current = self.current_selected_index == i 82 | line = "%s %d %s" % ("->" if is_current else " ", i, file_name) 83 | lcd.draw_string(x_offset, y_offset, line, lcd.WHITE, lcd.RED) 84 | # gc.collect() 85 | y_offset += 18 86 | if y_offset > lcd.height(): 87 | print(y_offset, lcd.height(), "y_offset > height(), break") 88 | break 89 | 90 | lcd.init() 91 | lcd.rotation(2) # Rotate the lcd 180deg 92 | 93 | def test_irq(gpio, pin_num=None): 94 | value = gpio.value() 95 | state = "released" if value else "pressed" 96 | print("key", gpio, state) 97 | global app, key1, key2 98 | if gpio is key2: 99 | app.on_top_button_changed(state) 100 | 101 | fm.register(board_info.BUTTON_A, fm.fpioa.GPIOHS21) 102 | fm.register(board_info.BUTTON_B, fm.fpioa.GPIOHS22) 103 | # fm.register(board_info.BUTTON_A, fm.fpioa.GPIOHS21, force=True) 104 | key1=GPIO(GPIO.GPIOHS21, GPIO.IN, GPIO.PULL_UP) 105 | key2=GPIO(GPIO.GPIOHS22, GPIO.IN, GPIO.PULL_UP) 106 | key1.irq(test_irq, GPIO.IRQ_BOTH, GPIO.WAKEUP_NOT_SUPPORT, 7) 107 | key2.irq(test_irq, GPIO.IRQ_BOTH, GPIO.WAKEUP_NOT_SUPPORT, 7) 108 | 109 | app = ExplorerApp() 110 | 111 | while True: 112 | if app.is_dirty: 113 | app.on_draw() 114 | time.sleep_ms(1) 115 | else: 116 | time.sleep_ms(100) 117 | -------------------------------------------------------------------------------- /res/icons/arrow_top_24x23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/arrow_top_24x23.jpg -------------------------------------------------------------------------------- /res/icons/battery/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery/0.jpg -------------------------------------------------------------------------------- /res/icons/battery/100.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery/100.jpg -------------------------------------------------------------------------------- /res/icons/battery/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery/20.jpg -------------------------------------------------------------------------------- /res/icons/battery/40.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery/40.jpg -------------------------------------------------------------------------------- /res/icons/battery/60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery/60.jpg -------------------------------------------------------------------------------- /res/icons/battery/80.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery/80.jpg -------------------------------------------------------------------------------- /res/icons/battery/charging_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery/charging_0.jpg -------------------------------------------------------------------------------- /res/icons/battery/charging_100.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery/charging_100.jpg -------------------------------------------------------------------------------- /res/icons/battery/charging_20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery/charging_20.jpg -------------------------------------------------------------------------------- /res/icons/battery/charging_40.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery/charging_40.jpg -------------------------------------------------------------------------------- /res/icons/battery/charging_60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery/charging_60.jpg -------------------------------------------------------------------------------- /res/icons/battery/charging_80.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery/charging_80.jpg -------------------------------------------------------------------------------- /res/icons/battery_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/battery_64x60.jpg -------------------------------------------------------------------------------- /res/icons/brightness_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/brightness_64x60.jpg -------------------------------------------------------------------------------- /res/icons/camera_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/camera_64x60.jpg -------------------------------------------------------------------------------- /res/icons/code_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/code_64x60.jpg -------------------------------------------------------------------------------- /res/icons/memory_card_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/memory_card_64x60.jpg -------------------------------------------------------------------------------- /res/icons/microphone_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/microphone_64x60.jpg -------------------------------------------------------------------------------- /res/icons/music_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/music_64x60.jpg -------------------------------------------------------------------------------- /res/icons/power_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/power_64x60.jpg -------------------------------------------------------------------------------- /res/icons/python_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/python_64x60.jpg -------------------------------------------------------------------------------- /res/icons/reboot_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/reboot_64x60.jpg -------------------------------------------------------------------------------- /res/icons/settings_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/settings_64x60.jpg -------------------------------------------------------------------------------- /res/icons/speedometer_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/speedometer_64x60.jpg -------------------------------------------------------------------------------- /res/icons/tools_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/tools_64x60.jpg -------------------------------------------------------------------------------- /res/icons/video_64x60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/icons/video_64x60.jpg -------------------------------------------------------------------------------- /res/provision.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/provision.jpg -------------------------------------------------------------------------------- /res/provision_old.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/res/provision_old.jpg -------------------------------------------------------------------------------- /resource.py: -------------------------------------------------------------------------------- 1 | app_list = [ 2 | {"id": "camera", "icon": "/sd/res/icons/camera_64x60.jpg"}, 3 | {"id": "explorer", "icon": "/sd/res/icons/memory_card_64x60.jpg"}, 4 | {"id": "settings", "icon": "/sd/res/icons/settings_64x60.jpg"}, 5 | {"id": "music", "icon": "/sd/res/icons/music_64x60.jpg"}, 6 | {"id": "video", "icon": "/sd/res/icons/video_64x60.jpg"}, 7 | {"id": "microphone", "icon": "/sd/res/icons/microphone_64x60.jpg"}, 8 | {"id": "tools", "icon": "/sd/res/icons/tools_64x60.jpg"}, 9 | {"id": "brightness", "icon": "/sd/res/icons/brightness_64x60.jpg"}, 10 | # {"id": "alert", "icon": "/sd/res/icons/alert_64x60.jpg"}, 11 | {"id": "power", "icon": "/sd/res/icons/power_64x60.jpg"}, 12 | {"id": "reboot", "icon": "/sd/res/icons/reboot_64x60.jpg"}, 13 | {"id": "code", "icon": "/sd/res/icons/code_64x60.jpg"}, 14 | {"id": "python", "icon": "/sd/res/icons/python_64x60.jpg"}, 15 | {"id": "battery", "icon": "/sd/res/icons/battery_64x60.jpg"}, 16 | {"id": "system_info", "icon": "/sd/res/icons/speedometer_64x60.jpg"}, 17 | ] 18 | battery_icon_list = [ 19 | "/sd/res/icons/battery/0.jpg", 20 | "/sd/res/icons/battery/20.jpg", 21 | "/sd/res/icons/battery/40.jpg", 22 | "/sd/res/icons/battery/60.jpg", 23 | "/sd/res/icons/battery/80.jpg", 24 | "/sd/res/icons/battery/100.jpg", 25 | ] 26 | battery_charging_icon_list = [ 27 | "/sd/res/icons/battery/charging_0.jpg", 28 | "/sd/res/icons/battery/charging_20.jpg", 29 | "/sd/res/icons/battery/charging_40.jpg", 30 | "/sd/res/icons/battery/charging_60.jpg", 31 | "/sd/res/icons/battery/charging_80.jpg", 32 | "/sd/res/icons/battery/charging_100.jpg", 33 | ] 34 | arrow_icon_path = "/sd/res/icons/arrow_top_24x23.jpg" 35 | provision_image_path = "/sd/res/provision.jpg" 36 | -------------------------------------------------------------------------------- /super_mario.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggfly/M5StickVComputer/eb081c77d797416f0474a68839615645567bb3b1/super_mario.wav -------------------------------------------------------------------------------- /traceback.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def format_tb(tb, limit): 4 | return ["traceback.format_tb() not implemented\n"] 5 | 6 | def format_exception_only(type, value): 7 | return [repr(value) + "\n"] 8 | 9 | def format_exception(etype, value, tb, limit=None, chain=True): 10 | return format_exception_only(etype, value) 11 | 12 | def print_exception(t, e, tb, limit=None, file=None, chain=True): 13 | if file is None: 14 | file = sys.stdout 15 | sys.print_exception(e, file) 16 | 17 | def print_exc(limit=None, file=None, chain=True): 18 | print_exception(*sys.exc_info(), limit=limit, file=file, chain=chain) 19 | 20 | def format_exc(limit=None, chain=True): 21 | return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain)) 22 | --------------------------------------------------------------------------------