├── requirements.txt ├── images └── preview.png ├── README.md ├── LICENSE ├── .gitignore └── main.py /requirements.txt: -------------------------------------------------------------------------------- 1 | loguru==0.7.3 2 | psutil==6.1.1 3 | requests==2.32.3 4 | -------------------------------------------------------------------------------- /images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKimZHT/MCompass-Sys-Monitor/master/images/preview.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MCompass-Sys-Monitor 2 | 3 | 基于 [chaosgoo/mcompass](https://github.com/chaosgoo/mcompass) 的 Minecraft 指南针硬件开发的系统性能监控表。 4 | 5 | ![Preview](./images/preview.png) 6 | 7 | ## 使用方式 8 | 9 | 将指南针连接电脑(不需要和该程序在同一台设备上),并将指南针配网连接到局域网中。 10 | 11 | 在 [Release](https://github.com/ChrisKimZHT/MCompass-Sys-Monitor/releases) 页下载对应平台的二进制文件,使用命令行运行,参数可见下文“运行参数”小节: 12 | 13 | ```shell 14 | ./MCompass-Sys-Monitor-Linux-x86_64-v1.0.0 --compass-ip 192.168.6.233 --interval 0.1 15 | ``` 16 | 17 | ## 运行参数 18 | 19 | | 参数 | 类型 | 默认值 | 说明 | 20 | | ---------------- | ------------ | ------ | ---------------------------- | 21 | | `--compass-ip` | `str` | 必填 | 指南针配网后的 IP 地址 | 22 | | `--monitor-type` | `str` | `cpu` | 监控类型,可选 `cpu`、`mem` | 23 | | `--interval` | `float` | `1.0` | 监控间隔,单位秒 | 24 | | `--half` | `store_true` | 否 | 仅使用指南针上半圈显示 | 25 | | `--silent` | `store_true` | 否 | 静默模式,不输出任何日志 | 26 | | `--logarithm` | `store_true` | 否 | 对数模式,低数值时指针更灵敏 | 27 | | `--animation` | `int` | 0 | 动画插值角度,0 为关闭动画 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Haotian Zou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | cover/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 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 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # poetry 99 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 100 | # This is especially recommended for binary packages to ensure reproducibility, and is more 101 | # commonly ignored for libraries. 102 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 103 | #poetry.lock 104 | 105 | # pdm 106 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 107 | #pdm.lock 108 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 109 | # in version control. 110 | # https://pdm.fming.dev/#use-with-ide 111 | .pdm.toml 112 | 113 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 114 | __pypackages__/ 115 | 116 | # Celery stuff 117 | celerybeat-schedule 118 | celerybeat.pid 119 | 120 | # SageMath parsed files 121 | *.sage.py 122 | 123 | # Environments 124 | .env 125 | .venv 126 | env/ 127 | venv/ 128 | ENV/ 129 | env.bak/ 130 | venv.bak/ 131 | 132 | # Spyder project settings 133 | .spyderproject 134 | .spyproject 135 | 136 | # Rope project settings 137 | .ropeproject 138 | 139 | # mkdocs documentation 140 | /site 141 | 142 | # mypy 143 | .mypy_cache/ 144 | .dmypy.json 145 | dmypy.json 146 | 147 | # Pyre type checker 148 | .pyre/ 149 | 150 | # pytype static type analyzer 151 | .pytype/ 152 | 153 | # Cython debug symbols 154 | cython_debug/ 155 | 156 | # PyCharm 157 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 158 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 159 | # and can be added to the global gitignore or merged into this file. For a more nuclear 160 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 161 | .idea/ 162 | 163 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import time 3 | 4 | import loguru 5 | import psutil 6 | import requests 7 | 8 | 9 | def percent_to_azimuth(percent: float, half: bool = False, log: bool = False) -> float: 10 | """convert usage percentage to azimuth angle 11 | azimuth : 0 -> up, 90 -> right, 180 -> down, 270 -> left 12 | ------ 13 | half: 1.8deg/pct, 0 -> left, 50 -> up, 100 -> right 14 | full: 3.6deg/pct, 0 -> left, 25 -> up, 50 -> right, 75 -> down, 100 -> left 15 | ------ 16 | half log: 0~10 -> 9deg/pct, 10~30 -> 4.5deg/pct, 30~60 -> 3deg/pct, 60~100 -> 2.25deg/pct 17 | full log: 0~10 -> 4.5deg/pct, 10~30 -> 2.25deg/pct, 30~60 -> 1.5deg/pct, 60~100 -> 1.125deg/pct 18 | """ 19 | pct_list = [10, 20, 30, 40] 20 | if half == True and log == False: 21 | deg_list = [1.8, 1.8, 1.8, 1.8] 22 | elif half == False and log == False: 23 | deg_list = [3.6, 3.6, 3.6, 3.6] 24 | elif half == True and log == True: 25 | deg_list = [4.5, 2.25, 1.5, 1.125] 26 | else: # half == False and log == True 27 | deg_list = [9, 4.5, 3, 2.25] 28 | azimuth = 0.0 29 | for i in range(4): 30 | azimuth += deg_list[i] * min(percent, pct_list[i]) 31 | percent -= pct_list[i] 32 | if percent <= 0.0: 33 | break 34 | azimuth = (azimuth - 90 + 360) % 360 35 | return azimuth 36 | 37 | 38 | def get_usage(typ: str) -> float: 39 | if typ == "cpu": 40 | return psutil.cpu_percent() 41 | elif typ == "mem": 42 | return psutil.virtual_memory().percent 43 | else: 44 | loguru.logger.error("Invalid monitor type") 45 | return 0.0 46 | 47 | 48 | def set_azimuth(azimuth: float) -> None: 49 | try: 50 | resp = requests.post(f"http://{args.compass_ip}/setAzimuth?azimuth={azimuth}", timeout=0.5) 51 | loguru.logger.info(f"Status: {resp.status_code}") 52 | except Exception as e: 53 | loguru.logger.error(f"Error: {e}") 54 | 55 | 56 | def set_azimuth_with_animation(cur_azimuth: float, tgt_azimuth: float, diff: int) -> None: 57 | cur_azimuth = (cur_azimuth + 90) % 360 58 | tgt_azimuth = (tgt_azimuth + 90) % 360 59 | 60 | while cur_azimuth != tgt_azimuth: 61 | if cur_azimuth < tgt_azimuth: 62 | cur_azimuth = min(cur_azimuth + diff, tgt_azimuth) 63 | else: 64 | cur_azimuth = max(cur_azimuth - diff, tgt_azimuth) 65 | set_azimuth((cur_azimuth - 90 + 360) % 360) 66 | 67 | 68 | def main(args: argparse.Namespace) -> None: 69 | cur_azimuth = 0.0 70 | while True: 71 | start_time = time.time() 72 | 73 | usage = get_usage(args.monitor_type) 74 | azimuth = percent_to_azimuth(usage, half=args.half, log=args.logarithm) 75 | loguru.logger.info(f"Usage: {usage}%, Azimuth: {azimuth}°") 76 | 77 | if args.animation == 0: 78 | set_azimuth(azimuth) 79 | else: 80 | set_azimuth_with_animation(cur_azimuth, azimuth, args.animation) 81 | cur_azimuth = azimuth 82 | 83 | elapsed_time = time.time() - start_time 84 | time.sleep(max(0, args.interval - elapsed_time)) 85 | 86 | 87 | if __name__ == "__main__": 88 | parser = argparse.ArgumentParser(description="MCompass System Monitor") 89 | parser.add_argument("--compass-ip", type=str, default="", help="IP address of the MCompass") 90 | parser.add_argument("--monitor-type", type=str, default="cpu", help="Monitor type: cpu/mem") 91 | parser.add_argument("--interval", type=float, default=1.0, help="Interval between each update") 92 | parser.add_argument("--silent", action='store_true', help="Silent mode") 93 | parser.add_argument("--half", action='store_true', help="Half circle mode") 94 | parser.add_argument("--logarithm", action='store_true', help="Logarithmic curve mode") 95 | parser.add_argument("--animation", type=int, default=0, help="Animation interpolation degree, 0 for no animation") 96 | args = parser.parse_args() 97 | if args.silent: 98 | loguru.logger.remove() 99 | if args.compass_ip == "": 100 | loguru.logger.warning("IP address of the MCompass is not provided in the argument, receive it from stdin") 101 | args.compass_ip = input("Enter the IP address of the MCompass: ") 102 | main(args) 103 | --------------------------------------------------------------------------------