├── sh-pc ├── update-lib.sh ├── install-circup.sh └── install-lib.sh ├── .gitignore ├── src ├── hid │ ├── joystick │ │ ├── input │ │ │ ├── joystick_input.py │ │ │ ├── pro_controller.py │ │ │ └── hori.py │ │ ├── joystick.py │ │ ├── __init__.py │ │ └── hori.py │ └── device │ │ ├── device.py │ │ ├── __init__.py │ │ ├── hori.py │ │ └── switch_pro.py ├── resources │ ├── macros │ │ ├── pokemon │ │ │ ├── za │ │ │ │ ├── battles.m │ │ │ │ ├── bench.m │ │ │ │ └── tranfer.m │ │ │ ├── swsh │ │ │ │ ├── eggs.m │ │ │ │ ├── regi.m │ │ │ │ └── common.m │ │ │ └── scarletviolet │ │ │ │ ├── common.m │ │ │ │ ├── bug.m │ │ │ │ └── eggs.m │ │ └── common.m │ └── config.json ├── boot.py ├── root_Hori │ ├── boot.py │ └── code.py ├── root_ProController │ ├── boot.py │ └── code.py ├── .vscode │ └── settings.json ├── lib │ ├── adafruit_httpserver │ │ ├── methods.py │ │ ├── exceptions.py │ │ ├── status.py │ │ ├── authentication.py │ │ ├── __init__.py │ │ ├── headers.py │ │ └── route.py │ ├── customize │ │ ├── datetime.py │ │ ├── device_info.py │ │ ├── wifi_connect.py │ │ ├── task_manager.py │ │ └── config.py │ ├── asyncio │ │ ├── manifest.py │ │ ├── __init__.py │ │ ├── event.py │ │ ├── lock.py │ │ ├── funcs.py │ │ └── task.py │ ├── adafruit_hid │ │ ├── consumer_control_code.py │ │ ├── __init__.py │ │ ├── consumer_control.py │ │ ├── mouse.py │ │ └── keyboard_layout_us.py │ ├── adafruit_ntp.py │ └── adafruit_ticks.py ├── macros │ ├── node.py │ ├── paras.py │ ├── action.py │ ├── __init__.py │ └── macro.py ├── code.py ├── http_server.py └── tcp_server.py ├── video-server-python ├── .gitignore ├── resources │ ├── font │ │ └── simsun.ttc │ ├── img │ │ └── battle_shiny.jpg │ └── config.json ├── capture │ ├── __init__.py │ └── video.py ├── prepare.sh ├── test_ui.py ├── .vscode │ └── settings.json ├── recognize │ ├── opencv │ │ ├── __init__.py │ │ ├── none_opencv.py │ │ ├── opencv.py │ │ └── battle_shiny.py │ ├── __init__.py │ └── frame.py ├── ui │ ├── __init__.py │ ├── video.py │ └── log.py ├── controller │ ├── __init__.py │ ├── macro.py │ └── send_order.py ├── datatype │ └── device.py └── main.py ├── imgs ├── devices.jpg ├── show.webp ├── recognition.webp └── registeel2.webp ├── firmware ├── circuitpython │ ├── 7.3.3 │ │ ├── esp32s3-n16r8 │ │ │ ├── hori │ │ │ │ ├── build.sh │ │ │ │ ├── firmware_7.3.3.bin │ │ │ │ ├── firmware_7.3.3.uf2 │ │ │ │ └── Dockerfile │ │ │ └── switch_pro │ │ │ │ ├── build.sh │ │ │ │ ├── firmware_7.3.3.bin │ │ │ │ ├── firmware_7.3.3.uf2 │ │ │ │ └── Dockerfile │ │ └── lolin-s2-mini │ │ │ ├── hori │ │ │ ├── build.sh │ │ │ ├── firmware_7.3.3.bin │ │ │ ├── firmware_7.3.3.uf2 │ │ │ └── Dockerfile │ │ │ └── switch_pro │ │ │ ├── firmware_7.3.3.bin │ │ │ ├── firmware_7.3.3.uf2 │ │ │ ├── build.sh │ │ │ └── Dockerfile │ └── 8.0.0 │ │ └── 支持设备.txt └── uf2 │ ├── tinyuf2-lolin_s2_mini-0.16.0 │ ├── combined.bin │ └── update-tinyuf2.uf2 │ └── tinyuf2-yd_esp32_s3_n16r8-0.16.0 │ ├── combined.bin │ └── update-tinyuf2.uf2 ├── NOTE.md ├── QA.md ├── run-json.txt ├── SCRIPTS.md └── README.md /sh-pc/update-lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | circup update -------------------------------------------------------------------------------- /sh-pc/install-circup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pip3 install --upgrade circup -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # src/lib/* 2 | !src/lib/customize 3 | .DS_Store 4 | test_ui -------------------------------------------------------------------------------- /src/hid/joystick/input/joystick_input.py: -------------------------------------------------------------------------------- 1 | class JoyStickInput: 2 | pass -------------------------------------------------------------------------------- /video-server-python/.gitignore: -------------------------------------------------------------------------------- 1 | env 2 | __pycache__ 3 | build 4 | *.spec 5 | dist 6 | output -------------------------------------------------------------------------------- /imgs/devices.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/imgs/devices.jpg -------------------------------------------------------------------------------- /imgs/show.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/imgs/show.webp -------------------------------------------------------------------------------- /src/resources/macros/pokemon/za/battles.m: -------------------------------------------------------------------------------- 1 | --谱通餐厅连战-- 2 | 3 | ZL:0.2->ZL|A:0.05->0.1->~ 4 | -------------------------------------------------------------------------------- /imgs/recognition.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/imgs/recognition.webp -------------------------------------------------------------------------------- /imgs/registeel2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/imgs/registeel2.webp -------------------------------------------------------------------------------- /src/boot.py: -------------------------------------------------------------------------------- 1 | import hid.device 2 | import hid.joystick 3 | hid.device.get_device(hid.device.Device_Switch_Pro).init_device() -------------------------------------------------------------------------------- /src/root_Hori/boot.py: -------------------------------------------------------------------------------- 1 | import hid.device 2 | 3 | device = hid.device.get_device(hid.device.Device_HORIPAD_S) 4 | device.init_device() -------------------------------------------------------------------------------- /src/resources/macros/pokemon/za/bench.m: -------------------------------------------------------------------------------- 1 | --长椅-- 2 | 3 | { 4 | LStick@0,127|A:0.05->LStick@0,127:0.05->~ 5 | }*180 6 | -------------------------------------------------------------------------------- /src/root_ProController/boot.py: -------------------------------------------------------------------------------- 1 | import hid.device 2 | 3 | device = hid.device.get_device(hid.device.Device_Switch_Pro) 4 | device.init_device() -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/esp32s3-n16r8/hori/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build . -t circuitpython 3 | docker run -d --rm -v $(PWD)/build:/root/build circuitpython 4 | -------------------------------------------------------------------------------- /video-server-python/resources/font/simsun.ttc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/video-server-python/resources/font/simsun.ttc -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/esp32s3-n16r8/switch_pro/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build . -t circuitpython 3 | docker run -d --rm -v $(PWD)/build:/root/build circuitpython 4 | -------------------------------------------------------------------------------- /video-server-python/resources/img/battle_shiny.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/video-server-python/resources/img/battle_shiny.jpg -------------------------------------------------------------------------------- /firmware/uf2/tinyuf2-lolin_s2_mini-0.16.0/combined.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/firmware/uf2/tinyuf2-lolin_s2_mini-0.16.0/combined.bin -------------------------------------------------------------------------------- /firmware/uf2/tinyuf2-yd_esp32_s3_n16r8-0.16.0/combined.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/firmware/uf2/tinyuf2-yd_esp32_s3_n16r8-0.16.0/combined.bin -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/lolin-s2-mini/hori/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build . -t circuitpython-lolin-s2-mini 3 | docker run -d --rm -v $(PWD)/build:/root/build circuitpython-lolin-s2-mini 4 | -------------------------------------------------------------------------------- /firmware/uf2/tinyuf2-lolin_s2_mini-0.16.0/update-tinyuf2.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/firmware/uf2/tinyuf2-lolin_s2_mini-0.16.0/update-tinyuf2.uf2 -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/esp32s3-n16r8/hori/firmware_7.3.3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/firmware/circuitpython/7.3.3/esp32s3-n16r8/hori/firmware_7.3.3.bin -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/esp32s3-n16r8/hori/firmware_7.3.3.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/firmware/circuitpython/7.3.3/esp32s3-n16r8/hori/firmware_7.3.3.uf2 -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/lolin-s2-mini/hori/firmware_7.3.3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/firmware/circuitpython/7.3.3/lolin-s2-mini/hori/firmware_7.3.3.bin -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/lolin-s2-mini/hori/firmware_7.3.3.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/firmware/circuitpython/7.3.3/lolin-s2-mini/hori/firmware_7.3.3.uf2 -------------------------------------------------------------------------------- /firmware/uf2/tinyuf2-yd_esp32_s3_n16r8-0.16.0/update-tinyuf2.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/firmware/uf2/tinyuf2-yd_esp32_s3_n16r8-0.16.0/update-tinyuf2.uf2 -------------------------------------------------------------------------------- /src/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.pylintEnabled": false, 3 | "python.languageServer": "Pylance", 4 | "python.analysis.diagnosticSeverityOverrides": { 5 | "reportMissingModuleSource": "none" 6 | } 7 | } -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/esp32s3-n16r8/switch_pro/firmware_7.3.3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/firmware/circuitpython/7.3.3/esp32s3-n16r8/switch_pro/firmware_7.3.3.bin -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/esp32s3-n16r8/switch_pro/firmware_7.3.3.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/firmware/circuitpython/7.3.3/esp32s3-n16r8/switch_pro/firmware_7.3.3.uf2 -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/lolin-s2-mini/switch_pro/firmware_7.3.3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/firmware/circuitpython/7.3.3/lolin-s2-mini/switch_pro/firmware_7.3.3.bin -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/lolin-s2-mini/switch_pro/firmware_7.3.3.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiantwf/esp32-circuitpython-switch-joystick/HEAD/firmware/circuitpython/7.3.3/lolin-s2-mini/switch_pro/firmware_7.3.3.uf2 -------------------------------------------------------------------------------- /src/hid/device/device.py: -------------------------------------------------------------------------------- 1 | class Device(object): 2 | def __init__(self): 3 | pass 4 | 5 | def init_device(self): 6 | pass 7 | 8 | def find_device(self): 9 | pass 10 | -------------------------------------------------------------------------------- /video-server-python/resources/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "recognize":{ 3 | "battle_shiny":{ 4 | "image":"resources/img/battle_shiny.jpg", 5 | "area":{ 6 | "x":865,"y":430,"width":95,"height":95 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /video-server-python/capture/__init__.py: -------------------------------------------------------------------------------- 1 | from capture import video 2 | import datatype.device as device 3 | 4 | def capture_video(pipe,dev:device.VideoDevice,display_width,display_height,display_fps): 5 | video.Video().run(pipe,dev,display_width,display_height,display_fps) 6 | -------------------------------------------------------------------------------- /video-server-python/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python3 -m venv env 3 | 4 | source ./env/bin/activate 5 | pip install opencv-python PySide6 ffmpeg-python pyinstaller auto-py-to-exe pillow --index-url http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com 6 | 7 | 8 | auto-py-to-exe -------------------------------------------------------------------------------- /src/resources/macros/pokemon/swsh/eggs.m: -------------------------------------------------------------------------------- 1 | --饲育屋取蛋-- 2 | 3 | { 4 | body: 5 | { 6 | LStick@-127,0:1.7 7 | 0.5 8 | LStick@127,0:1.8 9 | LStick@0,-127:0.5 10 | 0.1 11 | }*3 12 | A:0.1 13 | 0.8 14 | A:0.1 15 | 0.8 16 | { 17 | B:0.1 18 | 0.9 19 | }*8 20 | 1 21 | } -------------------------------------------------------------------------------- /sh-pc/install-lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | circup install asyncio adafruit-circuitpython-hid adafruit-circuitpython-ntp adafruit-circuitpython-httpserver 3 | circup install adafruit-circuitpython-ssd1327 adafruit-circuitpython-minimqtt 4 | circup install adafruit-circuitpython-aws-iot adafruit-circuitpython-ble adafruit-circuitpython-display_shapes -------------------------------------------------------------------------------- /src/resources/macros/pokemon/scarletviolet/common.m: -------------------------------------------------------------------------------- 1 | --打开宝可梦朱紫-- 2 | 3 | A:0.1 4 | 1.5 5 | A:0.1 6 | 19 7 | {5}?secondary 8 | A:0.1 9 | 1 10 | A:0.1 11 | 1 12 | A:0.1 13 | 21 14 | 15 | --重启宝可梦朱紫-- 16 | 17 | [common.close_game] 18 | [open_game] 19 | -------------------------------------------------------------------------------- /src/hid/device/__init__.py: -------------------------------------------------------------------------------- 1 | from hid.device import device, hori, switch_pro 2 | Device_HORIPAD_S = hori.Tag 3 | Device_Switch_Pro = switch_pro.Tag 4 | 5 | def get_device(tag)-> device.Device: 6 | if tag == Device_HORIPAD_S: 7 | return hori.Device_Horipad_S() 8 | elif tag == Device_Switch_Pro: 9 | return switch_pro.Device_Switch_Pro() 10 | else: 11 | return switch_pro.Device_Switch_Pro() 12 | -------------------------------------------------------------------------------- /firmware/circuitpython/8.0.0/支持设备.txt: -------------------------------------------------------------------------------- 1 | 测试版本 8.0.0-beta.4 2 | 3 | 直接使用circuitpython官方固件即可: 4 | https://circuitpython.org/downloads 5 | 6 | 已测试设备: 7 | Raspberry Pi Pico 8 | https://circuitpython.org/board/raspberry_pi_pico/ 9 | 10 | Raspberry Pi Pico W 11 | https://circuitpython.org/board/raspberry_pi_pico_w/ 12 | 13 | ESP32S3-N16R8 14 | https://circuitpython.org/board/yd_esp32_s3_n16r8/ 15 | 16 | LOLIN S2 Mini 17 | https://circuitpython.org/board/lolin_s2_mini/ -------------------------------------------------------------------------------- /src/resources/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "wifi": { 3 | "type": "wifi", 4 | "ssid": "test", 5 | "pwd": "test", 6 | "hostname": "ls2mini" 7 | }, 8 | "macros": { 9 | "autorun": { 10 | "stop":false, 11 | "name": "连续点击A", 12 | "loop": -1, 13 | "paras": { 14 | "secondary": "False" 15 | } 16 | } 17 | }, 18 | "web-server": { 19 | "running":false 20 | }, 21 | "tcp-server": { 22 | "running":false, 23 | "port":5000 24 | } 25 | } -------------------------------------------------------------------------------- /src/hid/joystick/joystick.py: -------------------------------------------------------------------------------- 1 | class JoyStick: 2 | pass 3 | 4 | async def start(self): 5 | pass 6 | 7 | def start_realtime(self): 8 | pass 9 | 10 | def stop_realtime(self): 11 | pass 12 | 13 | async def send_realtime_action(self,action:str): 14 | pass 15 | 16 | async def release(self,release_monotonic_ns:float = 0): 17 | pass 18 | 19 | async def do_action(self,action_line: str = ""): 20 | pass 21 | -------------------------------------------------------------------------------- /src/resources/macros/common.m: -------------------------------------------------------------------------------- 1 | --唤醒手柄-- 2 | 3 | LPress:0.1 4 | 1 5 | LPress:0.1 6 | 3 7 | 8 | --返回主界面-- 9 | 10 | Home:0.1 11 | 1.5 12 | 13 | --返回游戏界面(非Home页面)-- 14 | 15 | [common.return_home] 16 | A:0.1 17 | 1.5 18 | 19 | --关闭游戏-- 20 | 21 | [common.return_home] 22 | X:0.1 23 | 0.5 24 | A:0.1 25 | 5 26 | 27 | --连续点击A-- 28 | 29 | A:0.1 30 | 0.2 31 | 32 | --连续点击B-- 33 | 34 | B:0.1 35 | 0.2 36 | -------------------------------------------------------------------------------- /video-server-python/test_ui.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | from PySide6 import QtWidgets 4 | from ui.user_window import UserWindows 5 | def main(): 6 | _show() 7 | 8 | def _show(): 9 | app = QtWidgets.QApplication() 10 | _main_window = UserWindows(None,None,None,None,None) 11 | app.installEventFilter(_main_window) 12 | _main_window.setupUi() 13 | _main_window.show() 14 | ret = app.exec_() 15 | sys.exit(ret) 16 | 17 | 18 | if __name__ == "__main__": 19 | main() -------------------------------------------------------------------------------- /src/lib/adafruit_httpserver/methods.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2022 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | """ 5 | `adafruit_httpserver.methods` 6 | ==================================================== 7 | * Author(s): Michał Pokusa 8 | """ 9 | 10 | 11 | GET = "GET" 12 | 13 | POST = "POST" 14 | 15 | PUT = "PUT" 16 | 17 | DELETE = "DELETE" 18 | 19 | PATCH = "PATCH" 20 | 21 | HEAD = "HEAD" 22 | 23 | OPTIONS = "OPTIONS" 24 | 25 | TRACE = "TRACE" 26 | 27 | CONNECT = "CONNECT" 28 | -------------------------------------------------------------------------------- /src/lib/customize/datetime.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | def now(): 4 | now = time.localtime() 5 | str = "%02d/%02d/%02d %02d:%02d:%02d" % ( 6 | now[0], now[1], now[2], now[3], now[4], now[5]) 7 | return str 8 | 9 | 10 | def ntpSync(): 11 | try: 12 | import wifi 13 | import socketpool 14 | import rtc 15 | import adafruit_ntp 16 | ntp = adafruit_ntp.NTP(socketpool.SocketPool( 17 | wifi.radio), server='1.cn.pool.ntp.org', tz_offset=8) 18 | rtc.RTC().datetime = ntp.datetime 19 | except: 20 | pass 21 | return now() 22 | -------------------------------------------------------------------------------- /src/lib/customize/device_info.py: -------------------------------------------------------------------------------- 1 | def get_rom_info(): 2 | import os 3 | statvfs_fields = ['bsize', 'frsize', 'blocks', 4 | 'bfree', 'bavail', 'files', 'ffree', ] 5 | info = dict(zip(statvfs_fields, os.statvfs('/'))) 6 | return info['bsize'] * info['bfree'] / 1024 / 1024, info['bsize'] * info['blocks'] / 1024 / 1024 7 | 8 | 9 | def mem_free(): 10 | import gc 11 | m = gc.mem_free() / 1024 12 | return m 13 | 14 | def cpu_temperature(): 15 | import microcontroller 16 | try: 17 | return microcontroller.cpu.temperature 18 | except: 19 | return 0 -------------------------------------------------------------------------------- /video-server-python/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.languageServer": "Pylance", 3 | "python.analysis.diagnosticSeverityOverrides": { 4 | "reportMissingModuleSource": "none" 5 | }, 6 | "python.analysis.extraPaths": [ 7 | "", 8 | "c:\\Users\\radia\\.vscode\\extensions\\joedevivo.vscode-circuitpython-0.1.19-win32-x64\\stubs", 9 | "c:\\Users\\radia\\AppData\\Roaming\\Code\\User\\globalStorage\\joedevivo.vscode-circuitpython\\bundle\\20221020\\adafruit-circuitpython-bundle-py-20221020\\lib" 10 | ], 11 | "circuitpython.board.version": null, 12 | "python.linting.pylintEnabled": false 13 | } -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/lolin-s2-mini/switch_pro/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build --network host --build-arg http_proxy=http://192.168.50.2:10087 --build-arg https_proxy=http://192.168.50.2:10087 . -t circuitpython-lolin-s2-mini 3 | docker run -d --env http_proxy=http://192.168.50.2:10087 --env https_proxy=http://192.168.50.2:10087 --rm -v $(PWD)/build:/root/build circuitpython-lolin-s2-mini 4 | # docker run -it --env http_proxy=http://192.168.50.2:10087 --env https_proxy=http://192.168.50.2:10087 -v E:\workspace\pokemon\esp32-pokemon\firmware\lolin-s2-mini\switch_pro\build:/root/build circuitpython-lolin-s2-mini /bin/bash 5 | -------------------------------------------------------------------------------- /video-server-python/recognize/opencv/__init__.py: -------------------------------------------------------------------------------- 1 | from recognize import frame 2 | from recognize.opencv.battle_shiny import BattleShiny 3 | from recognize.opencv.none_opencv import NoneOpenCV 4 | 5 | _opencv_list = [] 6 | _opencv_list.append("定点闪(A连点)") 7 | 8 | def opencv_list(): 9 | return _opencv_list 10 | 11 | 12 | def create_instance(tag,frame:frame.Frame,opencv_processed_video_frame,controller_action_queue,log_udp_port = 41001): 13 | if tag == "定点闪(A连点)": 14 | return BattleShiny(frame,opencv_processed_video_frame,controller_action_queue,log_udp_port) 15 | else: 16 | return NoneOpenCV(frame,opencv_processed_video_frame,controller_action_queue,log_udp_port) 17 | 18 | 19 | -------------------------------------------------------------------------------- /video-server-python/ui/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | from PySide6 import QtWidgets 4 | from datatype.device import AudioDevice 5 | from ui.user_window import UserWindows 6 | def run(frame_queues,control_queues,dev:AudioDevice,video_with,video_height): 7 | _show(frame_queues,control_queues,dev,video_with,video_height) 8 | 9 | def _show(frame_queues,control_queues,dev:AudioDevice,video_with,video_height): 10 | app = QtWidgets.QApplication() 11 | _main_window = UserWindows(frame_queues,control_queues,dev,video_with,video_height) 12 | app.installEventFilter(_main_window) 13 | _main_window.setupUi() 14 | _main_window.show() 15 | ret = app.exec_() 16 | sys.exit(ret) -------------------------------------------------------------------------------- /src/hid/joystick/__init__.py: -------------------------------------------------------------------------------- 1 | import hid.device 2 | from hid.joystick.hori import JoyStick_HORI_S 3 | from hid.joystick.pro_controller import JoyStick_PRO_CONTROLLER 4 | 5 | class JoyStickFactory: 6 | _instance = None 7 | @staticmethod 8 | def get_instance(tag:str = ""): 9 | if JoyStickFactory._instance == None: 10 | if tag == hid.device.Device_HORIPAD_S: 11 | JoyStickFactory._instance = JoyStick_HORI_S() 12 | elif tag == hid.device.Device_Switch_Pro: 13 | JoyStickFactory._instance = JoyStick_PRO_CONTROLLER() 14 | else: 15 | JoyStickFactory._instance = JoyStick_PRO_CONTROLLER() 16 | return JoyStickFactory._instance -------------------------------------------------------------------------------- /src/lib/asyncio/manifest.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Damien P. George 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # 5 | # 6 | # This code comes from MicroPython, and has not been run through black or pylint there. 7 | # Altering these files significantly would make merging difficult, so we will not use 8 | # pylint or black. 9 | # pylint: skip-file 10 | # fmt: off 11 | 12 | # This list of frozen files doesn't include task.py because that's provided by the C module. 13 | freeze( 14 | "..", 15 | ( 16 | "uasyncio/__init__.py", 17 | "uasyncio/core.py", 18 | "uasyncio/event.py", 19 | "uasyncio/funcs.py", 20 | "uasyncio/lock.py", 21 | "uasyncio/stream.py", 22 | ), 23 | opt=3, 24 | ) 25 | -------------------------------------------------------------------------------- /src/macros/node.py: -------------------------------------------------------------------------------- 1 | class Node(object): 2 | def __init__(self, action_line: str): 3 | self._head: Node = self 4 | self._next: Node = None 5 | self._action_line: str = action_line 6 | 7 | def append(self, action_line: str): 8 | node = self 9 | while True: 10 | if node._next == None: 11 | break 12 | else: 13 | node = node._next 14 | node._next = Node(action_line) 15 | node._next._head = node._head 16 | return node._next 17 | 18 | @property 19 | def head(self): 20 | return self._head 21 | 22 | @property 23 | def next(self): 24 | return self._next 25 | 26 | @property 27 | def action(self): 28 | return self._action_line 29 | -------------------------------------------------------------------------------- /video-server-python/recognize/opencv/none_opencv.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from recognize import frame 3 | from recognize.opencv.opencv import OpenCV 4 | 5 | 6 | class NoneOpenCV(OpenCV): 7 | def __init__(self,frame:frame.Frame,opencv_processed_video_frame,controller_action_queue,log_udp_port): 8 | super().__init__(frame,opencv_processed_video_frame,controller_action_queue,log_udp_port) 9 | self._enable_send_action = False 10 | 11 | 12 | async def run(self): 13 | while True: 14 | data = await self._frame.get_frame() 15 | image = ( 16 | np 17 | .frombuffer(data, np.uint8) 18 | .reshape([self._frame.height, self._frame.width, 3]) 19 | ) 20 | self._opencv_processed_video_frame.put(image.tobytes(),False,0) -------------------------------------------------------------------------------- /video-server-python/ui/video.py: -------------------------------------------------------------------------------- 1 | 2 | from PySide6.QtCore import QThread,Signal 3 | from PySide6.QtGui import QImage 4 | 5 | 6 | class VideoThread(QThread): 7 | video_frame = Signal(QImage) 8 | def __init__(self, parent=None): 9 | QThread.__init__(self, parent) 10 | 11 | def set_input(self,width,height,channels,format,queue): 12 | self._width = width 13 | self._height = height 14 | self._channels = channels 15 | self._format = format 16 | self._queue = queue 17 | 18 | def run(self): 19 | while True: 20 | if self._queue != None : 21 | frame = self._queue.get() 22 | img = QImage(frame, self._width, self._height, self._channels*self._width,self._format) 23 | self.video_frame.emit(img) -------------------------------------------------------------------------------- /src/resources/macros/pokemon/scarletviolet/bug.m: -------------------------------------------------------------------------------- 1 | --无限复制道具(1.01版BUG,网友 燃烧の小宇宙 提供)-- 2 | 3 | # 准备工作:箱子处于1号,复制的坐骑置于队伍中第二个并选中,携带待复制道具,退出菜单,移动人物到灯塔小屋中 4 | # 参考视频:BV1gW4y1p7ZZ 5 | # 原地蹲几下提示开始 6 | { 7 | B:0.1 8 | 0.2 9 | }*7 10 | 1 11 | # 打开随身精灵列表 12 | X:0.15 13 | 0.5 14 | body: 15 | # 回到坐骑形态 16 | 0.5 17 | A:0.15 18 | 1.2 19 | { 20 | LStick@0,-127:0.15 21 | 0.12 22 | }*2 23 | A:0.1 24 | 0.7 25 | A:0.1 26 | 0.3 27 | A:0.15 28 | 4 29 | A:0.15 30 | 1 31 | # 打开盒子 32 | LStick@127,0:0.15 33 | 0.15 34 | { 35 | LStick@0,127:0.15 36 | 0.15 37 | }*2 38 | A:0.15 39 | 3.5 40 | # 切换模式 41 | { 42 | X:0.15 43 | 0.75 44 | }*2 45 | 0.25 46 | # 选中坐骑回收道具 47 | L:0.15 48 | 0.75 49 | A:0.15 50 | 1.2 51 | { 52 | LStick@0,127:0.15 53 | 0.12 54 | }*3 55 | A:0.15 56 | 1.5 57 | # 退出盒子还原状态 58 | B:0.15 59 | 4 60 | LStick@-127,0:0.15 61 | 0.25 -------------------------------------------------------------------------------- /video-server-python/recognize/__init__.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from recognize import frame, opencv 3 | 4 | def run(frame_queues,control_queues,video_with,video_height,fps = 5): 5 | f = frame.Frame(video_with,video_height,fps) 6 | loop = asyncio.get_event_loop() 7 | task1 = loop.create_task(f.loop_read(frame_queues[0])) 8 | task2 = loop.create_task(_task_manager(loop,f,frame_queues[2],control_queues[0],control_queues[1])) 9 | loop.run_forever() 10 | 11 | async def _task_manager(loop,f,opencv_processed_video_frame,opencv_processed_control_queue,controller_action_queue): 12 | loop = asyncio.get_event_loop() 13 | task = None 14 | while True: 15 | await asyncio.sleep(0.1) 16 | action = None 17 | try: 18 | action = opencv_processed_control_queue.get_nowait() 19 | except: 20 | pass 21 | if not action: 22 | continue 23 | if task: 24 | task.cancel() 25 | try: 26 | await task 27 | except asyncio.CancelledError: 28 | pass 29 | task = None 30 | task = loop.create_task(opencv.create_instance(action,f,opencv_processed_video_frame,controller_action_queue).run()) 31 | 32 | -------------------------------------------------------------------------------- /src/lib/customize/wifi_connect.py: -------------------------------------------------------------------------------- 1 | from . import config 2 | import wifi 3 | 4 | _config = config.Config() 5 | _ssid = _config.get("ssid", "wifi") 6 | _pwd = _config.get("pwd", "wifi") 7 | _hostname = _config.get("hostname", "wifi") 8 | _wifi_type = _config.get("type", "wifi") 9 | print(_wifi_type) 10 | print(_ssid) 11 | print(_pwd) 12 | print(_hostname) 13 | 14 | def connect(): 15 | if _hostname != None and _hostname != "": 16 | wifi.radio.hostname = _hostname 17 | if _wifi_type == "ap": 18 | wifi.radio.start_ap(_ssid, _pwd) 19 | else: 20 | wifi.radio.connect(_ssid, _pwd) 21 | 22 | def reconnect(): 23 | if ip_address() != None: 24 | return 25 | if _hostname != None and _hostname != "": 26 | wifi.radio.hostname = _hostname 27 | if _wifi_type == "ap": 28 | wifi.radio.start_ap(_ssid, _pwd) 29 | else: 30 | wifi.radio.connect(_ssid, _pwd) 31 | 32 | 33 | def disconnect(): 34 | try: 35 | if _wifi_type == "ap": 36 | wifi.radio.stop_ap() 37 | else: 38 | wifi.radio.stop_station() 39 | except: 40 | pass 41 | 42 | def ip_address(): 43 | if _wifi_type == 'ap': 44 | return wifi.radio.ipv4_address_ap 45 | return wifi.radio.ipv4_address -------------------------------------------------------------------------------- /src/lib/asyncio/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Damien P. George 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # 5 | # MicroPython uasyncio module 6 | # MIT license; Copyright (c) 2019 Damien P. George 7 | # 8 | # This code comes from MicroPython, and has not been run through black or pylint there. 9 | # Altering these files significantly would make merging difficult, so we will not use 10 | # pylint or black. 11 | # pylint: skip-file 12 | # fmt: off 13 | 14 | from .core import * 15 | 16 | __version__ = "0.5.23" 17 | __repo__ = "https://github.com/Adafruit/Adafruit_CircuitPython_asyncio.git" 18 | 19 | _attrs = { 20 | "wait_for": "funcs", 21 | "wait_for_ms": "funcs", 22 | "gather": "funcs", 23 | "Event": "event", 24 | "ThreadSafeFlag": "event", 25 | "Lock": "lock", 26 | "open_connection": "stream", 27 | "start_server": "stream", 28 | "StreamReader": "stream", 29 | "StreamWriter": "stream", 30 | } 31 | 32 | # Lazy loader, effectively does: 33 | # global attr 34 | # from .mod import attr 35 | def __getattr__(attr): 36 | mod = _attrs.get(attr, None) 37 | if mod is None: 38 | raise AttributeError(attr) 39 | value = getattr(__import__(mod, None, None, True, 1), attr) 40 | globals()[attr] = value 41 | return value 42 | -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/lolin-s2-mini/hori/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://learn.adafruit.com/building-circuitpython/build-circuitpython 2 | # https://docs.circuitpython.org/en/latest/BUILDING.html 3 | # https://learn.adafruit.com/building-circuitpython/espressif-build 4 | FROM python:3 5 | 6 | USER root 7 | 8 | RUN apt-get update && apt-get -y install \ 9 | git \ 10 | ninja-build cmake libusb-1.0-0 gettext 11 | 12 | WORKDIR /root 13 | # it's taking a long time 14 | RUN git clone -b 7.3.3 --depth=1 https://github.com/adafruit/circuitpython.git && \ 15 | cd circuitpython && \ 16 | make fetch-submodules 17 | 18 | WORKDIR /root/circuitpython/ports/espressif 19 | RUN esp-idf/install.sh 20 | 21 | WORKDIR /root/circuitpython/ports/espressif/boards 22 | RUN sed -i 's/USB_VID = 0x303A/USB_VID = 0x0F0D/g' lolin_s2_mini/mpconfigboard.mk && \ 23 | sed -i 's/USB_PID = 0x80C3/USB_PID = 0x00C1/g' lolin_s2_mini/mpconfigboard.mk && \ 24 | sed -i 's/USB_PRODUCT = "S2 Mini"/USB_PRODUCT = "HORIPAD S"/g' lolin_s2_mini/mpconfigboard.mk && \ 25 | sed -i 's/USB_MANUFACTURER = "Lolin"/USB_MANUFACTURER = "HORI CO.,LTD."/g' lolin_s2_mini/mpconfigboard.mk 26 | 27 | WORKDIR /root/circuitpython/ports/espressif 28 | 29 | VOLUME ["/root/build"] 30 | 31 | CMD ["/bin/bash", "-c", "source esp-idf/export.sh && make -j$(getconf _NPROCESSORS_ONLN) BOARD=lolin_s2_mini && cp -r build-lolin_s2_mini/* /root/build"] -------------------------------------------------------------------------------- /video-server-python/controller/__init__.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import multiprocessing 3 | import socket 4 | import datatype.device as device 5 | 6 | from controller.send_order import OrderSender 7 | 8 | _log_udp_port = 41001 9 | 10 | def run(dev:device.JoystickDevice,control_queues,log_udp_port=41001): 11 | global _log_udp_port 12 | _log_udp_port = log_udp_port 13 | sender = OrderSender(dev) 14 | loop = asyncio.get_event_loop() 15 | task1 = loop.create_task(sender.loop_run(dev)) 16 | task2 = loop.create_task(process_inputs(sender,control_queues[1])) 17 | task3 = loop.create_task(sender.loop_outputs(send_log)) 18 | loop.run_forever() 19 | 20 | async def send_log(data): 21 | udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 22 | udp_socket.sendto(data.encode("utf-8"), ("127.0.0.1", _log_udp_port)) 23 | 24 | async def process_inputs(sender,action_queue:multiprocessing.Queue): 25 | action = None 26 | while True: 27 | while True: 28 | try: 29 | action = action_queue.get_nowait() 30 | except: 31 | await asyncio.sleep(0.001) 32 | continue 33 | break 34 | msg = action.message 35 | await sender.add_order(msg.encode("utf-8")) 36 | if action.type == "realtime" and action.get("realtime") != "action_start" and action.get("realtime") != "action_stop": 37 | continue 38 | await send_log("发送命令:" + msg) -------------------------------------------------------------------------------- /src/lib/adafruit_hid/consumer_control_code.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2018 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid.consumer_control_code.ConsumerControlCode` 7 | ======================================================== 8 | 9 | * Author(s): Dan Halbert 10 | """ 11 | 12 | 13 | class ConsumerControlCode: 14 | """USB HID Consumer Control Device constants. 15 | 16 | This list includes a few common consumer control codes from 17 | https://www.usb.org/sites/default/files/hut1_21_0.pdf#page=118. 18 | """ 19 | 20 | # pylint: disable-msg=too-few-public-methods 21 | 22 | RECORD = 0xB2 23 | """Record""" 24 | FAST_FORWARD = 0xB3 25 | """Fast Forward""" 26 | REWIND = 0xB4 27 | """Rewind""" 28 | SCAN_NEXT_TRACK = 0xB5 29 | """Skip to next track""" 30 | SCAN_PREVIOUS_TRACK = 0xB6 31 | """Go back to previous track""" 32 | STOP = 0xB7 33 | """Stop""" 34 | EJECT = 0xB8 35 | """Eject""" 36 | PLAY_PAUSE = 0xCD 37 | """Play/Pause toggle""" 38 | MUTE = 0xE2 39 | """Mute""" 40 | VOLUME_DECREMENT = 0xEA 41 | """Decrease volume""" 42 | VOLUME_INCREMENT = 0xE9 43 | """Increase volume""" 44 | BRIGHTNESS_DECREMENT = 0x70 45 | """Decrease Brightness""" 46 | BRIGHTNESS_INCREMENT = 0x6F 47 | """Increase Brightness""" 48 | -------------------------------------------------------------------------------- /video-server-python/recognize/opencv/opencv.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import multiprocessing 3 | import socket 4 | from controller.macro import Macro 5 | from recognize import frame 6 | 7 | class OpenCV(object): 8 | def __init__(self,frame:frame.Frame,opencv_processed_video_frame:multiprocessing.Queue,controller_action_queue:multiprocessing.Queue,log_udp_port = 41001): 9 | self._frame = frame 10 | self._opencv_processed_video_frame = opencv_processed_video_frame 11 | self._controller_action_queue = controller_action_queue 12 | self._log_udp_port = log_udp_port 13 | self._enable_send_action = True 14 | 15 | 16 | def send_log(self,msg): 17 | udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 18 | udp_socket.sendto(msg.encode("utf-8"), ("127.0.0.1", self._log_udp_port)) 19 | 20 | 21 | async def send_action(self,m:Macro,timeout:float = None): 22 | try: 23 | await asyncio.wait_for(self._send_action(m),timeout=timeout) 24 | return True 25 | except asyncio.TimeoutError: 26 | return False 27 | 28 | async def _send_action(self,m:Macro): 29 | if not self._enable_send_action: 30 | return 31 | while True: 32 | try: 33 | self._controller_action_queue.put_nowait(m) 34 | except: 35 | await asyncio.sleep(0.1) 36 | return 37 | 38 | -------------------------------------------------------------------------------- /src/lib/customize/task_manager.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | 4 | class TaskManager(object): 5 | def __new__(cls, *args, **kwargs): 6 | if not hasattr(cls, '_instance'): 7 | cls._instance = super(TaskManager, cls).__new__(cls) 8 | return cls._instance 9 | 10 | _first = True 11 | 12 | def __init__(self): 13 | if TaskManager._first: 14 | TaskManager._first = False 15 | self._loop = asyncio.get_event_loop() 16 | self._dic_tasks = dict() 17 | 18 | def wait_forever(self): 19 | self._loop.run_forever() 20 | 21 | def create_task(self, coro, key=None): 22 | if key == None: 23 | self._loop.create_task(coro) 24 | return 25 | if self._dic_tasks.get(key) == None: 26 | task = self._loop.create_task(coro) 27 | self._dic_tasks[key] = task 28 | self._loop.create_task(self.wait_task(task, key)) 29 | 30 | def close_loop(self): 31 | self._loop.close() 32 | 33 | async def wait_task(self, task, key): 34 | try: 35 | await task 36 | print("{}任务完成".format(key)) 37 | except Exception as e: 38 | print(e) 39 | self._dic_tasks.pop(key) 40 | 41 | async def cancel_task(self, key: str): 42 | task = self._dic_tasks.get(key) 43 | if task == None: 44 | return 45 | task.cancel() 46 | await task 47 | -------------------------------------------------------------------------------- /video-server-python/ui/log.py: -------------------------------------------------------------------------------- 1 | 2 | import socket 3 | import time 4 | from PySide6.QtCore import QThread,Signal 5 | 6 | 7 | class LogThread(QThread): 8 | log = Signal(str) 9 | def __init__(self, parent=None): 10 | QThread.__init__(self, parent) 11 | self._udp_socket = None 12 | 13 | def set_port(self,port = 41001): 14 | self._port = port 15 | 16 | def run(self): 17 | while True: 18 | try: 19 | if self._udp_socket: 20 | s = self._udp_socket 21 | self._udp_socket = None 22 | s.close() 23 | self._udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 24 | local_addr = ("127.0.0.1", self._port) 25 | self._udp_socket.bind(local_addr) 26 | self._udp_socket.setblocking(False) 27 | while True: 28 | recv_data = None 29 | try: 30 | recv_data = self._udp_socket.recvfrom(1024) 31 | except Exception as e: 32 | pass 33 | if recv_data != None: 34 | recv_msg = recv_data[0].decode("utf-8").strip() 35 | self.log.emit(recv_msg) 36 | time.sleep(0.001) 37 | except: 38 | continue 39 | 40 | def quit(self): 41 | if self._udp_socket: 42 | self._udp_socket.close() -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/lolin-s2-mini/switch_pro/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://learn.adafruit.com/building-circuitpython/build-circuitpython 2 | # https://docs.circuitpython.org/en/latest/BUILDING.html 3 | # https://learn.adafruit.com/building-circuitpython/espressif-build 4 | FROM python:3 5 | 6 | USER root 7 | 8 | RUN apt-get update && apt-get -y install \ 9 | git \ 10 | ninja-build cmake libusb-1.0-0 gettext 11 | 12 | WORKDIR /root 13 | RUN git config --global http.postBuffer 1024M 14 | RUN git clone https://github.com/adafruit/circuitpython.git 15 | 16 | RUN cd circuitpython && \ 17 | git checkout '8.0.0-beta.3' && \ 18 | make fetch-submodules 19 | 20 | WORKDIR /root/circuitpython/ports/espressif 21 | RUN esp-idf/install.sh 22 | 23 | WORKDIR /root/circuitpython/ports/espressif/boards 24 | RUN sed -i 's/USB_VID = 0x303A/USB_VID = 0x057E/g' lolin_s2_mini/mpconfigboard.mk && \ 25 | sed -i 's/USB_PID = 0x80C3/USB_PID = 0x2009/g' lolin_s2_mini/mpconfigboard.mk && \ 26 | sed -i 's/USB_PRODUCT = "S2 Mini"/USB_PRODUCT = "Pro Controller"/g' lolin_s2_mini/mpconfigboard.mk && \ 27 | sed -i 's/USB_MANUFACTURER = "Lolin"/USB_MANUFACTURER = "Nintendo Co., Ltd."/g' lolin_s2_mini/mpconfigboard.mk 28 | RUN echo 'CIRCUITPY_ESP32_CAMERA = 0' >> lolin_s2_mini/mpconfigboard.mk 29 | WORKDIR /root/circuitpython/ports/espressif 30 | 31 | VOLUME ["/root/build"] 32 | 33 | CMD ["/bin/bash", "-c", "source esp-idf/export.sh && make -j$(getconf _NPROCESSORS_ONLN) BOARD=lolin_s2_mini && cp -r build-lolin_s2_mini/* /root/build"] 34 | -------------------------------------------------------------------------------- /src/lib/adafruit_hid/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid` 7 | ==================================================== 8 | 9 | This driver simulates USB HID devices. 10 | 11 | * Author(s): Scott Shawcroft, Dan Halbert 12 | 13 | Implementation Notes 14 | -------------------- 15 | **Software and Dependencies:** 16 | * Adafruit CircuitPython firmware for the supported boards: 17 | https://github.com/adafruit/circuitpython/releases 18 | """ 19 | 20 | # imports 21 | from __future__ import annotations 22 | 23 | try: 24 | from typing import Sequence 25 | import usb_hid 26 | except ImportError: 27 | pass 28 | 29 | __version__ = "5.3.6" 30 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_HID.git" 31 | 32 | 33 | def find_device( 34 | devices: Sequence[usb_hid.Device], *, usage_page: int, usage: int 35 | ) -> usb_hid.Device: 36 | """Search through the provided sequence of devices to find the one with the matching 37 | usage_page and usage.""" 38 | if hasattr(devices, "send_report"): 39 | devices = [devices] # type: ignore 40 | for device in devices: 41 | if ( 42 | device.usage_page == usage_page 43 | and device.usage == usage 44 | and hasattr(device, "send_report") 45 | ): 46 | return device 47 | raise ValueError("Could not find matching HID device.") 48 | -------------------------------------------------------------------------------- /src/resources/macros/pokemon/swsh/regi.m: -------------------------------------------------------------------------------- 1 | --雷吉艾勒奇-- 2 | 3 | { 4 | [pokemon.swsh.common.restart_game] 5 | body: 6 | A:0.1 7 | 1 8 | A:0.1 9 | 1.2 10 | A:0.1 11 | 6.65 12 | [pokemon.swsh.common.battle_check_shiny] 13 | A:0.1 14 | 0.5 15 | # 踩灯 16 | LStick@-127,0:1.8->LStick@9,0:0.05->0.3->LStick@0,-127:0.8->LStick@0,9:0.05->0.2->LStick@0,-127:0.3->LStick@9,0:0.05->0.3->LStick@-127,0:0.3->LStick@9,0:0.05->0.7->LStick@0,127:1.64->LStick@0,-9:0.05->0.3->LStick@127,0:2.5->LStick@-9,0:0.05->0.7->LStick@0,-127:0.44->LStick@0,9:0.05->0.3->LStick@-127,0:2.1->LStick@9,0:0.05->0.7->LStick@0,-127:0.42->LStick@0,9:0.05->0.3->LStick@127,0:2.7 17 | 3 18 | # 等待 并走到石像前 19 | A:0.1 20 | 1 21 | LStick@0,-127:1 22 | 0.2 23 | LStick@-127,0:0.54 24 | 0.2 25 | LStick@0,-127:0.1 26 | 0.2 27 | } 28 | 29 | --雷吉洛克/雷吉斯奇鲁-- 30 | 31 | { 32 | [pokemon.swsh.common.restart_game] 33 | body: 34 | A:0.1 35 | 1 36 | A:0.1 37 | 1.2 38 | A:0.1 39 | 7 40 | [pokemon.swsh.common.battle_check_shiny] 41 | A:0.1 42 | 0.5 43 | # 走到左上角墙角 44 | { 45 | LStick@-80,-100:0.4->B|LStick@-80,-100:0.1->~ 46 | } * 9 47 | 0.7 48 | # 踩灯 第3排 49 | LStick@0,127:1.79->LStick@0,-9:0.05 50 | 0.3 51 | LStick@127,0:2.5->LStick@-9,0:0.05 52 | 0.7 53 | # 踩灯 第2排 54 | LStick@0,-127:0.535->LStick@0,9:0.05 55 | 0.3 56 | LStick@-127,0:2.1->LStick@9,0:0.05 57 | 0.7 58 | # 踩灯 第1排 59 | LStick@0,-127:0.53->LStick@0,9:0.05 60 | 0.3 61 | LStick@127,0:2.7 62 | 3 63 | # 等待 并走到石像前 64 | A:0.1 65 | 1 66 | LStick@-127,0:0.5 67 | 0.2 68 | LStick@0,-127:1 69 | 0.2 70 | } -------------------------------------------------------------------------------- /src/lib/adafruit_httpserver/exceptions.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2022 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | """ 5 | `adafruit_httpserver.exceptions` 6 | ==================================================== 7 | * Author(s): Michał Pokusa 8 | """ 9 | 10 | 11 | class ServerStoppedError(Exception): 12 | """ 13 | Raised when ``.poll`` is called on a stopped ``Server``. 14 | """ 15 | 16 | 17 | class AuthenticationError(Exception): 18 | """ 19 | Raised by ``require_authentication`` when the ``Request`` is not authorized. 20 | """ 21 | 22 | 23 | class InvalidPathError(Exception): 24 | """ 25 | Parent class for all path related errors. 26 | """ 27 | 28 | 29 | class ParentDirectoryReferenceError(InvalidPathError): 30 | """ 31 | Path contains ``..``, a reference to the parent directory. 32 | """ 33 | 34 | def __init__(self, path: str) -> None: 35 | """Creates a new ``ParentDirectoryReferenceError`` for the ``path``.""" 36 | super().__init__(f"Parent directory reference in path: {path}") 37 | 38 | 39 | class BackslashInPathError(InvalidPathError): 40 | """ 41 | Backslash ``\\`` in path. 42 | """ 43 | 44 | def __init__(self, path: str) -> None: 45 | """Creates a new ``BackslashInPathError`` for the ``path``.""" 46 | super().__init__(f"Backslash in path: {path}") 47 | 48 | 49 | class ServingFilesDisabledError(Exception): 50 | """ 51 | Raised when ``root_path`` is not set and there is no handler for ``request``. 52 | """ 53 | 54 | 55 | class FileNotExistsError(Exception): 56 | """ 57 | Raised when a file does not exist. 58 | """ 59 | 60 | def __init__(self, path: str) -> None: 61 | """ 62 | Creates a new ``FileNotExistsError`` for the file at ``path``. 63 | """ 64 | super().__init__(f"File does not exist: {path}") 65 | -------------------------------------------------------------------------------- /src/lib/customize/config.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class Config(object): 5 | def __new__(cls, *args, **kwargs): 6 | if not hasattr(cls, '_instance'): 7 | cls._instance = super(Config, cls).__new__(cls) 8 | return cls._instance 9 | 10 | _first = True 11 | 12 | def __init__(self): 13 | if Config._first: 14 | Config._first = False 15 | self._config = dict() 16 | self._autorun = None 17 | self._load_file() 18 | 19 | def _load_file(self): 20 | try: 21 | f = open("/resources/config.json", "rt") 22 | c = json.load(f) 23 | f.close() 24 | except: 25 | if f != None: 26 | f.close() 27 | return 28 | self._config = self._analyze_config(c) 29 | 30 | def _analyze_config(self, d: dict, path: str = "", ret: dict = dict()) -> dict: 31 | for key in d.keys(): 32 | if path == "": 33 | p = key 34 | else: 35 | p = path + "." + key 36 | v = d.get(key) 37 | if type(v) is dict: 38 | if p == "macros.autorun": 39 | self._autorun = v 40 | ret = self._analyze_config(v, p, ret) 41 | else: 42 | ret[p] = v 43 | return ret 44 | 45 | def get(self, condition: str, type=""): 46 | if condition == None or condition == "": 47 | return None 48 | else: 49 | if type == None or type == "": 50 | key = condition 51 | else: 52 | key = "{}.{}".format(type, condition) 53 | v = self._config.get(key) 54 | return v 55 | 56 | def reset(self): 57 | self._config.clear() 58 | self._autorun = None 59 | self._load_file() 60 | 61 | @property 62 | def autorun(self): 63 | return self._autorun -------------------------------------------------------------------------------- /video-server-python/controller/macro.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | 4 | class Action(object): 5 | def __init__(self) -> None: 6 | self._dict = dict() 7 | self.ts = time.monotonic() 8 | 9 | @property 10 | def message(self): 11 | return json.dumps(self._dict) 12 | 13 | @property 14 | def type(self): 15 | return "action" 16 | 17 | def get(self,key): 18 | return self._dict.get(key) 19 | 20 | class Macro(Action): 21 | def __init__(self,stop:bool,name:str,loop:int = 1,paras:dict = dict()) -> None: 22 | super().__init__() 23 | if stop: 24 | self._dict["stop"] = True 25 | if name == None: 26 | return 27 | else: 28 | self._dict["stop"] = False 29 | self._dict["name"] = name 30 | self._dict["loop"] = loop 31 | self._dict["paras"] = paras 32 | 33 | @property 34 | def type(self): 35 | return "macro" 36 | 37 | 38 | macro_action_clear = Macro(True,name=None) 39 | macro_close_game = Macro(True,name="common.close_game",loop=1) 40 | macro_press_button_a_loop = Macro(False,name="common.press_button_a",loop=100) 41 | 42 | class Realtime(Action): 43 | def __init__(self,action_line:str) -> None: 44 | super().__init__() 45 | self._dict["realtime"] = action_line 46 | 47 | @property 48 | def type(self): 49 | return "realtime" 50 | 51 | realtime_action_start = Realtime("action_start") 52 | realtime_action_stop = Realtime("action_stop") 53 | 54 | _public_macro_dict = dict() 55 | _public_macro_dict["雷吉艾勒奇"] = Macro(True,name="雷吉艾勒奇(定点)",loop=99999999,paras={"secondary":"False"}) 56 | _public_macro_dict["雷吉洛克/雷吉斯奇鲁"] = Macro(True,name="雷吉洛克/雷吉斯奇鲁(定点)",loop=99999999,paras={"secondary":"False"}) 57 | _public_macro_dict["饲育屋取蛋"] = Macro(True,name="饲育屋取蛋",loop=1000) 58 | 59 | def public_macros(): 60 | return _public_macro_dict.keys() 61 | 62 | def get_public_macro(key) -> Macro: 63 | return _public_macro_dict.get(key) -------------------------------------------------------------------------------- /src/lib/adafruit_httpserver/status.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2022 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | """ 5 | `adafruit_httpserver.status` 6 | ==================================================== 7 | * Author(s): Dan Halbert, Michał Pokusa 8 | """ 9 | 10 | 11 | class Status: # pylint: disable=too-few-public-methods 12 | """HTTP status code.""" 13 | 14 | def __init__(self, code: int, text: str): 15 | """ 16 | Define a status code. 17 | 18 | :param int code: Numeric value: 200, 404, etc. 19 | :param str text: Short phrase: "OK", "Not Found', etc. 20 | """ 21 | self.code = code 22 | self.text = text 23 | 24 | def __repr__(self): 25 | return f'Status({self.code}, "{self.text}")' 26 | 27 | def __str__(self): 28 | return f"{self.code} {self.text}" 29 | 30 | def __eq__(self, other: "Status"): 31 | return self.code == other.code and self.text == other.text 32 | 33 | 34 | OK_200 = Status(200, "OK") 35 | 36 | CREATED_201 = Status(201, "Created") 37 | 38 | ACCEPTED_202 = Status(202, "Accepted") 39 | 40 | NO_CONTENT_204 = Status(204, "No Content") 41 | 42 | PARTIAL_CONTENT_206 = Status(206, "Partial Content") 43 | 44 | TEMPORARY_REDIRECT_307 = Status(307, "Temporary Redirect") 45 | 46 | PERMANENT_REDIRECT_308 = Status(308, "Permanent Redirect") 47 | 48 | BAD_REQUEST_400 = Status(400, "Bad Request") 49 | 50 | UNAUTHORIZED_401 = Status(401, "Unauthorized") 51 | 52 | FORBIDDEN_403 = Status(403, "Forbidden") 53 | 54 | NOT_FOUND_404 = Status(404, "Not Found") 55 | 56 | METHOD_NOT_ALLOWED_405 = Status(405, "Method Not Allowed") 57 | 58 | TOO_MANY_REQUESTS_429 = Status(429, "Too Many Requests") 59 | 60 | INTERNAL_SERVER_ERROR_500 = Status(500, "Internal Server Error") 61 | 62 | NOT_IMPLEMENTED_501 = Status(501, "Not Implemented") 63 | 64 | SERVICE_UNAVAILABLE_503 = Status(503, "Service Unavailable") 65 | -------------------------------------------------------------------------------- /src/macros/paras.py: -------------------------------------------------------------------------------- 1 | class Paras(object): 2 | def __init__(self, default_para: dict,para:dict): 3 | self._paras = None 4 | if default_para != None: 5 | self._paras = default_para.copy() 6 | else: 7 | self._paras = dict() 8 | 9 | if para != None: 10 | for key in para.keys(): 11 | v = para[key] 12 | if v != None and v != "": 13 | self._paras[key] = v 14 | locals().update(self._paras) 15 | 16 | def exec_str(self,key): 17 | try: 18 | key = key.replace("|space|", " ", -1) 19 | exec(key) 20 | except: 21 | return 22 | 23 | def get_bool(self,key)->bool: 24 | try: 25 | key = key.replace("|space|", " ", -1) 26 | v = eval(key) 27 | except: 28 | return False 29 | if v == None: 30 | return False 31 | elif (type(v) is bool): 32 | return v 33 | elif (type(v) is str) and v.lower() == "true": 34 | return True 35 | return False 36 | 37 | def get_int(self,key)->int: 38 | try: 39 | key = key.replace("|space|", " ", -1) 40 | v = eval(key) 41 | except: 42 | return 0 43 | 44 | if v == None: 45 | return 0 46 | elif (type(v) is int): 47 | return v 48 | elif (type(v) is float): 49 | return int(v) 50 | elif (type(v) is str): 51 | return int(float(v)) 52 | return 0 53 | 54 | def get_float(self, key) -> float: 55 | try: 56 | key = key.replace("|space|", " ", -1) 57 | v = eval(key) 58 | except: 59 | return 0 60 | 61 | if v == None: 62 | return 0 63 | elif (type(v) is int): 64 | return float(v) 65 | elif (type(v) is float): 66 | return v 67 | elif (type(v) is str): 68 | return float(v) 69 | return 0 70 | -------------------------------------------------------------------------------- /src/resources/macros/pokemon/za/tranfer.m: -------------------------------------------------------------------------------- 1 | --原地传送-- 2 | 3 | Plus:0.1 4 | 0.3 5 | LStick@-*x_offset*-,-*y_offset*-:-*delay*- 6 | 0.1 7 | A:0.1 8 | 0.5 9 | A:0.1 10 | A:0.1 11 | 3 12 | 13 | --18区刷暴飞龙(走到门口)-- 14 | 15 | LStick@0,-127:0.03->LStick@0,-127|B:0.02->LStick@0,-127:1 16 | 0.3 17 | Plus:0.1 18 | 0.3 19 | LStick@-64,64:0.1 20 | 0.1 21 | A:0.1 22 | 0.5 23 | A:0.1 24 | A:0.1 25 | 3 26 | 27 | --20区 左侧大范围覆盖-- 28 | 29 | EXEC>enter_pokemonCenter_x_offset=-60;enter_pokemonCenter_y_offset=-127;enter_pokemonCenter_delay=0.25 30 | EXEC>leave_pokemonCenter_x_offset=-30;leave_pokemonCenter_y_offset=127;leave_pokemonCenter_delay=0.2 31 | EXEC>wholeDay_flag=False 32 | EXEC>cycles=33 33 | body: 34 | EXEC>index=0 35 | [pokecenter_bench] 36 | { 37 | EXEC>index=index+1 38 | LStick@0,-127:0.03->LStick@0,-127|B:0.02->LStick@0,-127:1 39 | 0.3 40 | A:0.1 41 | 2.5 42 | LStick@-75,-127:0.03->LStick@-75,-127|B:0.02->LStick@-75,-127:5->~ 43 | LStick@75,127:5.5->A:0.1 44 | 3 45 | EXEC>flag=index 58 | A:0.1 59 | 0.5 60 | A:0.1 61 | A:0.1 62 | 3 63 | 64 | --宝可梦中心 长椅休息-- 65 | 66 | Plus:0.1 67 | 0.3 68 | LStick@-*enter_pokemonCenter_x_offset*-,-*enter_pokemonCenter_y_offset*-:-*enter_pokemonCenter_delay*- 69 | 0.1 70 | [do_transfer] 71 | LStick@127,-50:0.03->LStick@127,-50|B:0.02->LStick@127,-50:1.8->~ 72 | LStick@30,-127:1.6 73 | { 74 | A:0.05->0.05->~ 75 | }*10*3 76 | 15 77 | { 78 | { 79 | LStick@0,127|A:0.05->LStick@0,127:0.05->~ 80 | }*10*3 81 | 15 82 | }?wholeDay_flag 83 | Plus:0.1 84 | 0.3 85 | LStick@-*leave_pokemonCenter_x_offset*-,-*leave_pokemonCenter_y_offset*-:-*leave_pokemonCenter_delay*- 86 | 0.1 87 | [do_transfer] 88 | 2 89 | -------------------------------------------------------------------------------- /src/code.py: -------------------------------------------------------------------------------- 1 | import hid.joystick 2 | import hid.device 3 | device = hid.device.Device_Switch_Pro 4 | hid.joystick.JoyStickFactory.get_instance(device) 5 | 6 | 7 | import macros 8 | import time 9 | import customize.task_manager as task_manager 10 | import customize.config as config 11 | 12 | def main(): 13 | c = config.Config() 14 | tm = task_manager.TaskManager() 15 | joystick = hid.joystick.JoyStickFactory.get_instance() 16 | tm.create_task(joystick.start()) 17 | web_running = False 18 | tcp_running = False 19 | try: 20 | import wifi 21 | web_running = c.get("web-server.running") 22 | tcp_running = c.get("tcp-server.running") 23 | except: 24 | pass 25 | web_running = type(web_running) is bool and web_running 26 | tcp_running = type(tcp_running) is bool and tcp_running 27 | if web_running or tcp_running: 28 | import customize.wifi_connect as wifi_connect 29 | for i in range(1,10): 30 | try: 31 | wifi_connect.connect() 32 | break 33 | except: 34 | if i>=5: 35 | raise 36 | print(wifi_connect.ip_address()) 37 | print(wifi.radio.hostname) 38 | import customize.datetime 39 | print(customize.datetime.ntpSync()) 40 | if web_running: 41 | import http_server as http 42 | tm.create_task(http.serve(), "") 43 | if tcp_running: 44 | from tcp_server import TcpServer 45 | port = 5000 46 | try: 47 | port = int(c.get("tcp-server.port")) 48 | except: 49 | pass 50 | if port < 0 or port > 65535: 51 | port = 5000 52 | tm.create_task(TcpServer().start_serve(port)) 53 | span = time.monotonic() 54 | if span < 15: 55 | time.sleep(15 - span) 56 | macros.action_queue_task_start() 57 | macros.auto_run() 58 | 59 | tm.wait_forever() 60 | tm.close_loop() 61 | 62 | 63 | if __name__ == '__main__': 64 | main() -------------------------------------------------------------------------------- /src/root_Hori/code.py: -------------------------------------------------------------------------------- 1 | import hid.joystick 2 | import hid.device 3 | device = hid.device.Device_HORIPAD_S 4 | hid.joystick.JoyStickFactory.get_instance(device) 5 | 6 | 7 | import macros 8 | import time 9 | import customize.task_manager as task_manager 10 | import customize.config as config 11 | import customize.datetime 12 | 13 | def main(): 14 | c = config.Config() 15 | tm = task_manager.TaskManager() 16 | joystick = hid.joystick.JoyStickFactory.get_instance() 17 | tm.create_task(joystick.start()) 18 | web_running = False 19 | tcp_running = False 20 | try: 21 | import wifi 22 | web_running = c.get("web-server.running") 23 | tcp_running = c.get("tcp-server.running") 24 | except: 25 | pass 26 | web_running = type(web_running) is bool and web_running 27 | tcp_running = type(tcp_running) is bool and tcp_running 28 | if web_running or tcp_running: 29 | import customize.wifi_connect as wifi_connect 30 | for i in range(1,10): 31 | try: 32 | wifi_connect.connect() 33 | break 34 | except: 35 | if i>=5: 36 | raise 37 | print(wifi_connect.ip_address()) 38 | print(wifi.radio.hostname) 39 | print(customize.datetime.ntpSync()) 40 | if web_running: 41 | import http_server as http 42 | tm.create_task(http.serve(), "") 43 | if tcp_running: 44 | from tcp_server import TcpServer 45 | port = 5000 46 | try: 47 | port = int(c.get("tcp-server.port")) 48 | except: 49 | pass 50 | if port < 0 or port > 65535: 51 | port = 5000 52 | tm.create_task(TcpServer().start_serve(port)) 53 | span = time.monotonic() 54 | if span < 15: 55 | time.sleep(15 - span) 56 | macros.action_queue_task_start() 57 | macros.auto_run() 58 | 59 | tm.wait_forever() 60 | tm.close_loop() 61 | 62 | 63 | if __name__ == '__main__': 64 | main() -------------------------------------------------------------------------------- /src/root_ProController/code.py: -------------------------------------------------------------------------------- 1 | import hid.joystick 2 | import hid.device 3 | device = hid.device.Device_Switch_Pro 4 | hid.joystick.JoyStickFactory.get_instance(device) 5 | 6 | 7 | import macros 8 | import time 9 | import customize.task_manager as task_manager 10 | import customize.config as config 11 | import customize.datetime 12 | 13 | def main(): 14 | c = config.Config() 15 | tm = task_manager.TaskManager() 16 | joystick = hid.joystick.JoyStickFactory.get_instance() 17 | tm.create_task(joystick.start()) 18 | web_running = False 19 | tcp_running = False 20 | try: 21 | import wifi 22 | web_running = c.get("web-server.running") 23 | tcp_running = c.get("tcp-server.running") 24 | except: 25 | pass 26 | web_running = type(web_running) is bool and web_running 27 | tcp_running = type(tcp_running) is bool and tcp_running 28 | if web_running or tcp_running: 29 | import customize.wifi_connect as wifi_connect 30 | for i in range(1,10): 31 | try: 32 | wifi_connect.connect() 33 | break 34 | except: 35 | if i>=5: 36 | raise 37 | print(wifi_connect.ip_address()) 38 | print(wifi.radio.hostname) 39 | print(customize.datetime.ntpSync()) 40 | if web_running: 41 | import http_server as http 42 | tm.create_task(http.serve(), "") 43 | if tcp_running: 44 | from tcp_server import TcpServer 45 | port = 5000 46 | try: 47 | port = int(c.get("tcp-server.port")) 48 | except: 49 | pass 50 | if port < 0 or port > 65535: 51 | port = 5000 52 | tm.create_task(TcpServer().start_serve(port)) 53 | span = time.monotonic() 54 | if span < 15: 55 | time.sleep(15 - span) 56 | macros.action_queue_task_start() 57 | macros.auto_run() 58 | 59 | tm.wait_forever() 60 | tm.close_loop() 61 | 62 | 63 | if __name__ == '__main__': 64 | main() -------------------------------------------------------------------------------- /video-server-python/recognize/frame.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import multiprocessing 3 | import time 4 | 5 | class Frame(object): 6 | def __new__(cls, *args, **kwargs): 7 | if not hasattr(cls, '_instance'): 8 | cls._instance = super(Frame, cls).__new__(cls) 9 | return cls._instance 10 | 11 | _first = True 12 | 13 | def __init__(self,width = 960,height = 540,fps = 5): 14 | if Frame._first: 15 | Frame._first = False 16 | self._queue = asyncio.Queue(1) 17 | self._raw = None 18 | self._width = width 19 | self._height = height 20 | self._fps = fps 21 | self._last_set_frame_monotonic_ns = time.monotonic_ns() 22 | 23 | def set_frame_nowait(self,data)->bool: 24 | if time.monotonic_ns() - self._last_set_frame_monotonic_ns < 1000000000/self._fps: 25 | return False 26 | if not self._queue.empty(): 27 | self._queue.get_nowait() 28 | try: 29 | self._queue.put_nowait(data) 30 | self._last_set_frame_monotonic_ns = time.monotonic_ns() 31 | return True 32 | except asyncio.QueueFull: 33 | return False 34 | 35 | async def get_frame(self): 36 | return await self._queue.get() 37 | 38 | def get_frame_nowait(self): 39 | try: 40 | return self._queue.get_nowait() 41 | except asyncio.QueueEmpty: 42 | return None 43 | 44 | async def loop_read(self,process_queue:multiprocessing.Queue): 45 | data = None 46 | while True: 47 | try: 48 | while True: 49 | _data = process_queue.get_nowait() 50 | data = _data 51 | except: 52 | pass 53 | if data == None: 54 | await asyncio.sleep(0.01) 55 | continue 56 | if self.set_frame_nowait(data): 57 | data == None 58 | await asyncio.sleep(0.01) 59 | @property 60 | def width(self): 61 | return self._width 62 | @property 63 | def height(self): 64 | return self._height -------------------------------------------------------------------------------- /src/lib/adafruit_httpserver/authentication.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2022 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | """ 5 | `adafruit_httpserver.authentication` 6 | ==================================================== 7 | * Author(s): Michał Pokusa 8 | """ 9 | 10 | try: 11 | from typing import Union, List 12 | except ImportError: 13 | pass 14 | 15 | from binascii import b2a_base64 16 | 17 | from .exceptions import AuthenticationError 18 | from .request import Request 19 | 20 | 21 | class Basic: 22 | """Represents HTTP Basic Authentication.""" 23 | 24 | def __init__(self, username: str, password: str) -> None: 25 | self._value = b2a_base64(f"{username}:{password}".encode()).decode().strip() 26 | 27 | def __str__(self) -> str: 28 | return f"Basic {self._value}" 29 | 30 | 31 | class Bearer: 32 | """Represents HTTP Bearer Token Authentication.""" 33 | 34 | def __init__(self, token: str) -> None: 35 | self._value = token 36 | 37 | def __str__(self) -> str: 38 | return f"Bearer {self._value}" 39 | 40 | 41 | def check_authentication(request: Request, auths: List[Union[Basic, Bearer]]) -> bool: 42 | """ 43 | Returns ``True`` if request is authorized by any of the authentications, ``False`` otherwise. 44 | 45 | Example:: 46 | 47 | check_authentication(request, [Basic("username", "password")]) 48 | """ 49 | 50 | auth_header = request.headers.get("Authorization") 51 | 52 | if auth_header is None: 53 | return False 54 | 55 | return any(auth_header == str(auth) for auth in auths) 56 | 57 | 58 | def require_authentication(request: Request, auths: List[Union[Basic, Bearer]]) -> None: 59 | """ 60 | Checks if the request is authorized and raises ``AuthenticationError`` if not. 61 | 62 | If the error is not caught, the server will return ``401 Unauthorized``. 63 | 64 | Example:: 65 | 66 | require_authentication(request, [Basic("username", "password")]) 67 | """ 68 | 69 | if not check_authentication(request, auths): 70 | raise AuthenticationError( 71 | "Request is not authenticated by any of the provided authentications" 72 | ) 73 | -------------------------------------------------------------------------------- /src/lib/adafruit_httpserver/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2022 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | """ 5 | `adafruit_httpserver` 6 | ================================================================================ 7 | 8 | Socket based HTTP Server for CircuitPython 9 | 10 | 11 | * Author(s): Dan Halbert, Michał Pokusa 12 | 13 | Implementation Notes 14 | -------------------- 15 | 16 | **Software and Dependencies:** 17 | 18 | * Adafruit CircuitPython firmware for the supported boards: 19 | https://github.com/adafruit/circuitpython/releases 20 | """ 21 | 22 | __version__ = "4.1.0" 23 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer.git" 24 | 25 | 26 | from .authentication import ( 27 | Basic, 28 | Bearer, 29 | check_authentication, 30 | require_authentication, 31 | ) 32 | from .exceptions import ( 33 | ServerStoppedError, 34 | AuthenticationError, 35 | InvalidPathError, 36 | ParentDirectoryReferenceError, 37 | BackslashInPathError, 38 | ServingFilesDisabledError, 39 | FileNotExistsError, 40 | ) 41 | from .headers import Headers 42 | from .methods import ( 43 | GET, 44 | POST, 45 | PUT, 46 | DELETE, 47 | PATCH, 48 | HEAD, 49 | OPTIONS, 50 | TRACE, 51 | CONNECT, 52 | ) 53 | from .mime_types import MIMETypes 54 | from .request import QueryParams, FormData, Request 55 | from .response import ( 56 | Response, 57 | FileResponse, 58 | ChunkedResponse, 59 | JSONResponse, 60 | Redirect, 61 | ) 62 | from .server import ( 63 | Server, 64 | NO_REQUEST, 65 | CONNECTION_TIMED_OUT, 66 | REQUEST_HANDLED_NO_RESPONSE, 67 | REQUEST_HANDLED_RESPONSE_SENT, 68 | ) 69 | from .status import ( 70 | Status, 71 | OK_200, 72 | CREATED_201, 73 | ACCEPTED_202, 74 | NO_CONTENT_204, 75 | PARTIAL_CONTENT_206, 76 | TEMPORARY_REDIRECT_307, 77 | PERMANENT_REDIRECT_308, 78 | BAD_REQUEST_400, 79 | UNAUTHORIZED_401, 80 | FORBIDDEN_403, 81 | NOT_FOUND_404, 82 | METHOD_NOT_ALLOWED_405, 83 | TOO_MANY_REQUESTS_429, 84 | INTERNAL_SERVER_ERROR_500, 85 | NOT_IMPLEMENTED_501, 86 | SERVICE_UNAVAILABLE_503, 87 | ) 88 | -------------------------------------------------------------------------------- /NOTE.md: -------------------------------------------------------------------------------- 1 | ## 开发笔记(不定期更新) 2 | 3 | #### 2022/09/19 4 | 5 | - 找到Lolin-S2-Mini开发板,购买,加修改固件,尝试把成本降到15元以内。等待到货后进行进一步尝试。(已完成测试) 6 | 7 | - ~~尝试使用adafruit-circuitpython-httpserver库,进行WEB服务支持。ESP32N16R8测试时内存减少速度过快,每隔10多秒会消耗完所有内存,并启动GC,不是很稳定。计划更换其他方式。~~ (已优化) 8 | 9 | - 优化了雷吉洛克的测试脚本 10 | - 编译Lolin-S2-Mini固件,由于网络因素,始终无法构建Docker镜像,预计明后天编译,等开发板到货,进行测试。 11 | 12 | #### 2022/09/20 13 | 14 | - 调整项目目录结构 15 | - 添加UDP服务端与异步任务管理器 16 | - Lolin-S2-Mini修改vid,pid版固件编译 17 | - bug修改 18 | 19 | #### 2022/09/21 20 | 21 | - Lolin-S2-Mini 设备测试通过 22 | - 添加HTTP管理功能接口 23 | - bug修改 24 | - 更新README文档,追加Lolin-S2-Mini操作说明 25 | 26 | #### 2022/09/22 27 | 28 | - 删除UDP服务(性能因素,与HTTP只保留1个) 29 | - 增加HTTP脚本控制功能 30 | 31 | #### 2022/09/23 32 | 33 | - HTTP控制功能基本完成(状态查询,启动,停止,脚本参数设置) 34 | 35 | #### 2022/09/24 36 | 37 | - 配置文件增加是否启动WEB管理选项(降低性能消耗) 38 | - 增加CPU温度显示 39 | - 优化HID控制 40 | 41 | #### 2022/09/25 42 | 43 | - 异步休眠不能使用float参数,Bug修改 44 | - 自定义脚本表达式支持 45 | - 准备开始更多脚本移植 46 | 47 | #### 2022/09/26 48 | 49 | - 依赖库上传(由于现在国内无法远程安装/更新库,所以打包进项目) 50 | 51 | #### 2022/09/27 52 | 53 | - 雷吉艾勒奇(定点)脚本制作 54 | - opencv图像识别系统搭建 55 | 56 | #### 2022/09/28 - 2022/10/02 57 | 58 | - PC端UI界面开发 59 | - 设备端TCP协议管理功能开发(WIFI断开自动重连支持,HTTP 重连暂不支持) 60 | - 设备端脚本按键准确度优化 61 | - 自动刷定点闪(图片识别方式)开发完成 62 | 63 | #### 2022/10/03 64 | 65 | - 摄像头采集API更换 ffmpeg->dshow(win),mac还未测试,假期结束后进行测试 66 | - ESP32连接NS后,自动运行唤醒手柄脚本 67 | - 运送实时命令ESP32部分程序初版开发 68 | 69 | #### 2022/10/04 - 2022/10/06 70 | 71 | - 键盘模拟NS键位 72 | - 脚本/实时命令切换 73 | 74 | #### 2022/10/11 75 | 76 | - 脚本追加连续按键语法 77 | 78 | #### 2022/10/17 79 | 80 | - 手柄固件从Hori Pad S 改为 Pro Controller,增加稳定性(测试阶段,暂时保留Hori固件支持) 81 | 82 | #### 2022/10/31 83 | 84 | - CircuitPython官方固件8.0.0-beta.4支持。(不再需要根据设备编译固件) 85 | 86 | #### 2022/11/16 87 | 88 | - 脚本功能扩展('EXEC>' 命令) 89 | - 朱紫野餐取蛋、孵蛋脚本更新 90 | 91 | #### 2023/05/20 - 1 92 | 93 | - CircuitPython官方固件8.1.0-rc.0支持 94 | - HTTP SERVER 更新至最新版本 95 | 96 | #### 2023/05/20 - 2 97 | 98 | - CircuitPython官方固件8.1.0-rc.0存在问题,无法接收到USB_HID数据,请使用8.0.5版本固件 99 | 100 | #### 2023/07/28 101 | 102 | - 依赖库升级,全部更新至最新版本 103 | - 脚本延时方案调整,操作精度要求高的脚本需要调整(已调整雷吉洛克) 104 | - CircuitPython官方固件测试,现只支持8.0.5固件,8.1.0以上版本固件对于供电要求更高,在Switch底座上无法正常运行 105 | - YD-ESP32S3-N16R8没有8.0.5版本固件,最低支持版本8.1.0。可以使用LOLIN S3 8.0.5版本固件替代 106 | -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/esp32s3-n16r8/hori/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://learn.adafruit.com/building-circuitpython/build-circuitpython 2 | # https://docs.circuitpython.org/en/latest/BUILDING.html 3 | # https://learn.adafruit.com/building-circuitpython/espressif-build 4 | FROM python:3 5 | 6 | USER root 7 | 8 | RUN apt-get update && apt-get -y install \ 9 | git \ 10 | ninja-build cmake libusb-1.0-0 gettext 11 | 12 | WORKDIR /root 13 | # it's taking a long time 14 | RUN git clone -b 7.3.3 --depth=1 https://github.com/adafruit/circuitpython.git && \ 15 | cd circuitpython && \ 16 | make fetch-submodules 17 | 18 | WORKDIR /root/circuitpython/ports/espressif 19 | RUN esp-idf/install.sh 20 | 21 | WORKDIR /root/circuitpython/ports/espressif/boards 22 | # https://github.com/adafruit/circuitpython/tree/main/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8r2 23 | RUN cp -r espressif_esp32s3_devkitc_1_n8r2 espressif_esp32s3_devkitc_1_n16r8 && \ 24 | sed -i 's/ESP32-S3-DevKitC-1-N8R2/ESP32-S3-DevKitC-1-N16R8/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.h && \ 25 | sed -i 's/USB_VID = 0x303A/USB_VID = 0x0F0D/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 26 | sed -i 's/USB_PID = 0x7003/USB_PID = 0x00C1/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 27 | sed -i 's/USB_PRODUCT = "ESP32-S3-DevKitC-1-N8R2"/USB_PRODUCT = "HORIPAD S"/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 28 | sed -i 's/USB_MANUFACTURER = "Espressif"/USB_MANUFACTURER = "HORI CO.,LTD."/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 29 | # CIRCUITPY_ESP_FLASH_MODE DIO -> QIO 30 | sed -i 's/dio/qio/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 31 | # CIRCUITPY_ESP_FLASH_SIZE 8MB -> 16MB 32 | sed -i 's/8MB/16MB/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 33 | echo 'CIRCUITPY_ESP32_CAMERA = 0' >> espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 34 | # QUAD -> OCTAL 35 | sed -i 's/CONFIG_SPIRAM_MODE_QUAD/CONFIG_SPIRAM_MODE_OCT/g' espressif_esp32s3_devkitc_1_n16r8/sdkconfig && \ 36 | # 2MB -> 8MB 37 | sed -i 's/2097152/8388608/g' espressif_esp32s3_devkitc_1_n16r8/sdkconfig 38 | 39 | WORKDIR /root/circuitpython/ports/espressif 40 | 41 | VOLUME ["/root/build"] 42 | 43 | CMD ["/bin/bash", "-c", "source esp-idf/export.sh && make -j$(getconf _NPROCESSORS_ONLN) BOARD=espressif_esp32s3_devkitc_1_n16r8 && cp -r build-espressif_esp32s3_devkitc_1_n16r8/* /root/build"] 44 | -------------------------------------------------------------------------------- /firmware/circuitpython/7.3.3/esp32s3-n16r8/switch_pro/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://learn.adafruit.com/building-circuitpython/build-circuitpython 2 | # https://docs.circuitpython.org/en/latest/BUILDING.html 3 | # https://learn.adafruit.com/building-circuitpython/espressif-build 4 | FROM python:3 5 | 6 | USER root 7 | 8 | RUN apt-get update && apt-get -y install \ 9 | git \ 10 | ninja-build cmake libusb-1.0-0 gettext 11 | 12 | WORKDIR /root 13 | # it's taking a long time 14 | RUN git clone -b 7.3.3 --depth=1 https://github.com/adafruit/circuitpython.git && \ 15 | cd circuitpython && \ 16 | make fetch-submodules 17 | 18 | WORKDIR /root/circuitpython/ports/espressif 19 | RUN esp-idf/install.sh 20 | 21 | WORKDIR /root/circuitpython/ports/espressif/boards 22 | # https://github.com/adafruit/circuitpython/tree/main/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8r2 23 | RUN cp -r espressif_esp32s3_devkitc_1_n8r2 espressif_esp32s3_devkitc_1_n16r8 && \ 24 | sed -i 's/ESP32-S3-DevKitC-1-N8R2/ESP32-S3-DevKitC-1-N16R8/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.h && \ 25 | sed -i 's/USB_VID = 0x303A/USB_VID = 0x057E/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 26 | sed -i 's/USB_PID = 0x7003/USB_PID = 0x2009/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 27 | sed -i 's/USB_PRODUCT = "ESP32-S3-DevKitC-1-N8R2"/USB_PRODUCT = "Pro Controller"/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 28 | sed -i 's/USB_MANUFACTURER = "Espressif"/USB_MANUFACTURER = "Nintendo Co., Ltd."/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 29 | # CIRCUITPY_ESP_FLASH_MODE DIO -> QIO 30 | sed -i 's/dio/qio/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 31 | # CIRCUITPY_ESP_FLASH_SIZE 8MB -> 16MB 32 | sed -i 's/8MB/16MB/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 33 | echo 'CIRCUITPY_ESP32_CAMERA = 0' >> espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 34 | # QUAD -> OCTAL 35 | sed -i 's/CONFIG_SPIRAM_MODE_QUAD/CONFIG_SPIRAM_MODE_OCT/g' espressif_esp32s3_devkitc_1_n16r8/sdkconfig && \ 36 | # 2MB -> 8MB 37 | sed -i 's/2097152/8388608/g' espressif_esp32s3_devkitc_1_n16r8/sdkconfig 38 | 39 | WORKDIR /root/circuitpython/ports/espressif 40 | 41 | VOLUME ["/root/build"] 42 | 43 | CMD ["/bin/bash", "-c", "source esp-idf/export.sh && make -j$(getconf _NPROCESSORS_ONLN) BOARD=espressif_esp32s3_devkitc_1_n16r8 && cp -r build-espressif_esp32s3_devkitc_1_n16r8/* /root/build"] 44 | -------------------------------------------------------------------------------- /QA.md: -------------------------------------------------------------------------------- 1 | # 常见问题汇总 2 | 3 | ## 设备选择 4 | 5 | ```text 6 | Q: 我该选择什么型号单片机?XXXX单片机是否可以使用? 7 | A: 原则上 [固件下载链接](https://circuitpython.org/downloads) 包含的设备都可以使用。 8 | 我测试过的设备包含:LOLIN S2 Mini、ESP32-S3-DevKitC-1-N16R8、Raspberry Pi Pico (W)。 9 | 其中LOLIN S2 Mini有10%左右的网友购买后刷入circuitpython固件后,PC无法识别,猜测是渠道的硬件问题,请大家谨慎选购。 10 | ``` 11 | 12 | ```text 13 | Q: 能否提供单片机的购买链接? 14 | A: 抱歉,由于我也没有单片机购买渠道,所以我无法对购买商铺进行任何推荐,请大家自行寻找靠谱选购。 15 | ``` 16 | 17 | ```text 18 | Q: 我该使用什么连接线? 19 | A: 随便一款手机数据线都可以支持,不过Switch端请不要使用Type-C直接连接,请使用底座或OTG转接口,直接连接可能会有主从识别问题。 20 | (我自己的Type-c To Type-c 的线无法正常连接单片机,无法确定是否是线的问题) 21 | ``` 22 | 23 | ## 固件刷写 24 | 25 | ```text 26 | Q: 7.3.3版本固件与8.0.0-beta版本固件我该选择哪一个? 27 | A: 如果你对开发有一定了解,并有使用TCP网络远程控制的需求,建议使用7.3.3版本固件。 28 | 新手建议直接使用8.0.0-beta版本固件,8.0.0-beta版本固件,请自行 [固件下载链接](https://circuitpython.org/downloads) 寻找对应设备下载。 29 | ``` 30 | 31 | ```text 32 | Q: 我想刷写7.3.3版固件,我该刷写哪一个文件?能否使用官网的官方固件? 33 | A: 由于7.3.3版本固件限制,必须针对不同设备进行修改再次编译,才能使用,所以如果没有专业的技能不建议使用。 34 | 其中LOLIN S2 Mini与ESP32-S3-DevKitC-1-N16R8我提供了编译好的版本。 35 | 以LOLIN S2 Mini为例,请刷写 "firmware/circuitpython/7.3.3/lolin-s2-mini/switch_pro/firmware_7.3.3.bin" 固件文件。 36 | ``` 37 | 38 | ```text 39 | Q: RP2040芯片(Raspberry Pi Pico等)的单片机如何刷写固件? 40 | A: 请参照设备官网提供的方式进行固件刷写操作。 41 | ``` 42 | 43 | ```text 44 | Q: LOLIN S2 Mini设备刷写完固件后,系统无法识别出卷标为CIRCUITPT的U盘怎么办? 45 | A: 如果发生了这个问题,那可能就要恭喜你,你中奖了,这个问题可能是我上边说的设备问题,我无法解决。 46 | 只能通过其他方式再次刷写固件,确定自己刷写流程没有问题。 47 | 建议使用https://circuitpython.org/board/lolin_s2_mini/页面提供的TinyUF2的固件刷写方式再次进行刷写,如果还是无法使用,建议联系购买商家处理。 48 | ``` 49 | 50 | ## switch连接 51 | 52 | ```text 53 | Q: 我把单片机插入到Switch上没有反应怎么办? 54 | A: 需要进行如下检查: 55 | 1、 由于游戏只识别第一个连接的手柄,请在插入单片机前断连自己的无线手柄(switch pro是一个黑色的小圆点,按下断连设备); 56 | 2、 请查看主机设置,是否启用了USB手柄连接; 57 | 3、 如果仍然无法使用,请在PC使用页面 https://gamepad-tester.com/ 进行测试,看是否能检测到手柄连接,脚本是否正确运行; 58 | 4、 如果上述都没有问题,连接Switch仍无法识别,请更换手柄引擎Switch Pro -> Hori Pad S 59 | ``` 60 | 61 | ```text 62 | Q: 我如何更换手柄引擎? 63 | A: 8.0.0以上版本,直接把 src/root_Hori 目录下所有文件,替换到单片机根目录即可。 64 | 7.3.3版本,需要下载并修改固件源代码,并且把设备信息修改为Hori Pad S的信息 65 | 可以参照 firmware/circuitpython/7.3.3/lolin-s2-mini/hori 目录下方法修改,然后重新编译固件,并重新刷写。更新固件后,再把 src/root_Hori 目录下所有文件,替换到单片机根目录即可。 66 | 67 | ``` 68 | 69 | ## 脚本问题 70 | 71 | ```text 72 | Q: 默认启动脚本是什么?如何修改默认启动脚本? 73 | A: 默认启动脚本为A键连点。修改默认启动脚本方法为:修改 src/resources/config.json 文件的 macros -> autorun 节点。 74 | 具体修改方法参照 run-json.txt 文件。 75 | ``` 76 | 77 | ```text 78 | Q: 如何修改或新建脚本? 79 | A: 请参照 SCRIPTS.md 说明进行操作。 80 | ``` 81 | -------------------------------------------------------------------------------- /video-server-python/controller/send_order.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from io import BytesIO 3 | import datatype.device as device 4 | 5 | class OrderSender(object): 6 | def __init__(self,dev:device.JoystickDevice): 7 | self._queue = asyncio.Queue(100) 8 | self._queue_output = asyncio.Queue(100) 9 | 10 | async def loop_run(self,dev): 11 | while True: 12 | try: 13 | reader,writer = await asyncio.open_connection(dev.host,dev.port) 14 | while True: 15 | try: 16 | await asyncio.sleep(0.2) 17 | action = None 18 | try: 19 | action = self._queue.get_nowait() 20 | except: 21 | pass 22 | bytes = BytesIO() 23 | try: 24 | while True: 25 | ret = await asyncio.wait_for(reader.read(1024),timeout=0.001) 26 | bytes.write(ret) 27 | except asyncio.TimeoutError: 28 | pass 29 | bytes.flush() 30 | bytes.seek(0) 31 | lines = bytes.readlines() 32 | if len(lines) > 0: 33 | for line in lines: 34 | line = line.decode("utf-8").strip() 35 | if line == "" or line=="ping": 36 | continue 37 | if self._queue_output.full(): 38 | self._queue_output.get_nowait() 39 | self._queue_output.put_nowait(line) 40 | bytes.close() 41 | 42 | if action == None: 43 | continue 44 | writer.write(action) 45 | await writer.drain() 46 | except Exception as e: 47 | print(e) 48 | break 49 | except Exception as e: 50 | print(e) 51 | 52 | async def add_order(self,action:str): 53 | await self._queue.put(action) 54 | 55 | async def loop_outputs(self,func): 56 | while True: 57 | data = await self._queue_output.get() 58 | if func != None: 59 | await func("控制器:" + data) 60 | -------------------------------------------------------------------------------- /run-json.txt: -------------------------------------------------------------------------------- 1 | nc -v 192.168.50.120 5000 2 | TCP相关修改,由于8.0.0-beta.5版本系统TCP接受数据阻塞问题,导致TCP连接后,系统被阻塞,无法进行其他操作。 3 | 所以TCP连接做个临时修改: 4 | 使用TCP远程管理时,进行TCP连接后脚本运行会中断。 5 | 这时候可以发送TCP控制命令,发送后并不会马上运行脚本。 6 | 这时请不要多次发送命令,断开TCP连接,即可运行之前发送命令。 7 | 8 | LOLIN-S2-Mini 8.0.0-beta.5版本固件WIFI感觉稳定性更差了,今天我还没成功连上我的路由器。 9 | 10 | 11 | // 停止脚本 12 | { "stop" : true } 13 | 14 | { "stop" : true, "name": "common.wakeup_joystick" } 15 | 16 | 17 | // -----------注意事项:--------------- 18 | // 在配置启动参数时,所有的 "stop" : true 修改为 "stop" : false 19 | 20 | 21 | // 朱紫野餐取蛋 22 | // 注意事项: 23 | // 启动脚本前,请先把角色移动到可以进行野餐的位置,并保存进度,默认会重启游戏并读取进度; 24 | // 启动脚本前,请根据自己 超级花生酱三明治 配方的位置,修改脚本; 25 | // 参数介绍: 26 | // loop: 脚本循环次数 27 | // secondary: 购买游戏用户是否为主机绑定用户,True为非绑定用户,False为绑定用户。(非绑定用户需要网络验证游戏能否运行,会增加等待时间) 28 | // get_eggs_times: 取蛋次数,2分钟取蛋一次,默认半个小时(单次三明治效果为半小时) 29 | TCP命令 30 | { "stop" : true, "name": "朱紫野餐", "loop": 1, "paras": { "secondary": "False", "get_eggs_times": 15 } } 31 | 配置文件 32 | { "stop" : false, "name": "朱紫野餐", "loop": 2, "paras": { "secondary": "False", "get_eggs_times": 15 } } 33 | 34 | // 朱紫孵蛋 35 | // 注意事项: 36 | // 起始位置,桌台市学校后侧空地(侧方跳跃滑翔过去) 37 | // 启动脚本前,请把菜单光标停留在盒子位置,然后关闭菜单。并且骑上坐骑,并且把角色放在一个空旷方便转圈移动的位置 38 | // 启动脚本前,请一定要根据自己要孵化的宝可梦设置好参数,并且身上只携带一只火焰身躯的宝可梦(据说火焰身躯已经不起作用,但是没有测试,也懒得测试了) 39 | // 参数介绍: 40 | // loop: 固定为1 41 | // start_col: 起始列 (0-5) 42 | // last_col: 终止列 (0-5) 43 | // last_page: 终止页面,起始页面与终止页面相同设置为0 44 | // cycles: 孵化周期(参照神奇宝贝百科数据设置) 45 | // flame_body: 排头精灵是否为火焰身躯(据说这代取消了火焰身躯的设定,由于我未测试,所以保留设定,默认为True) 46 | TCP命令 47 | { "stop" : true, "name": "朱紫孵蛋", "loop": 1, "paras": { "start_col": 0, "last_col": 2, "last_page": 1, "cycles": 20, "flame_body": "True" } } 48 | 配置文件 49 | { "stop" : false, "name": "朱紫孵蛋", "loop": 1, "paras": { "start_col": 0, "last_col": 5, "last_page": 1, "cycles": 20, "flame_body": "True" } } 50 | 51 | // 朱紫放生 52 | // 注意事项: 53 | // 启动脚本前,请把菜单光标停留在盒子位置,然后关闭菜单 54 | // 启动脚本前,请一定要设置好参数 55 | // 建议运行时有人值守,朱紫有时候会有一些卡顿,导致菜单弹出速度过慢(小概率随机出现),会影响脚本运行,由于此脚本运行速度较快,并且卡顿的概率不算太高,所以不想增加每一步操作的等待延时间。 56 | // 参数介绍: 57 | // loop: 固定为1 58 | // start_col: 起始列 (0-5) 59 | // last_col: 终止列 (0-5) 60 | // last_page: 终止页面,起始页面与终止页面相同设置为0 61 | TCP命令 62 | { "stop" : true, "name": "朱紫放生", "loop": 1, "paras": { "start_col": 0, "last_col": 4, "last_page": 6 } } 63 | 配置文件 64 | { "stop" : false, "name": "朱紫放生", "loop": 1, "paras": { "start_col": 0, "last_col": 1, "last_page": 5 } } 65 | 66 | // 朱紫无限复制道具(1.01版BUG,网友 燃烧の小宇宙 提供) 67 | // 准备工作:箱子处于1号,复制的坐骑置于队伍中第二个并选中,携带待复制道具,退出菜单,移动人物到灯塔小屋中 68 | // 参数介绍: 69 | // loop: 999 70 | TCP命令 71 | { "stop" : true, "name": "朱紫无限复制道具", "loop": 999} 72 | 配置文件 73 | { "stop" : false, "name": "朱紫无限复制道具", "loop": 999} 74 | 75 | 76 | TCP命令 77 | { "stop" : true, "loop": -1, "name": "连续点击A" } 78 | 配置文件 79 | { "stop" : false, "name": "朱紫无限复制道具", "loop": 999} 80 | -------------------------------------------------------------------------------- /src/hid/device/hori.py: -------------------------------------------------------------------------------- 1 | import usb_hid 2 | from adafruit_hid import find_device 3 | from hid.device import device 4 | 5 | Tag = "HORIPAD S" 6 | 7 | _HORIPAD_S_DESCRIPTOR = bytes(( 8 | 0x05, 0x01, # Generic desktop controls 9 | 0x09, 0x05, # Joystick 10 | 0xA1, 0x01, # Application 11 | # Buttons (2 bytes) 12 | 0x15, 0x00, # button off state 13 | 0x25, 0x01, # button on state 14 | 0x35, 0x00, # button off state 15 | 0x45, 0x01, # button on state 16 | 0x75, 0x01, # 1 bit per report field 17 | 0x95, 0x0E, # 14 report fields (14 buttons) 18 | 0x05, 0x09, # Buttons (section 12) 19 | 0x19, 0x01, 20 | 0x29, 0x0E, 21 | 0x81, 0x02, # Variable input 22 | 0x95, 0x02, # 2 report fields (empty 2 bits) 23 | 0x81, 0x01, # Array input 24 | # HAT switch 25 | 0x05, 0x01, # Generic desktop controls 26 | 0x25, 0x07, # 8 valid HAT states, sending 0x08 = nothing pressed 27 | 0x46, 0x3B, 0x01, # HAT "rotation" 28 | 0x75, 0x04, # 4 bits per report field 29 | # 1 report field (a nibble containing entire HAT state) 30 | 0x95, 0x01, 31 | 0x65, 0x14, # unit degrees 32 | 0x09, 0x39, # Hat switch (section 4.3) 33 | 0x81, 0x42, # Variable input, null state 34 | 0x65, 0x00, # No units 35 | 0x95, 0x01, # 1 report field (empty upper nibble) 36 | 0x81, 0x01, # Array input 37 | # Joystick (4 bytes) 38 | 0x26, 0xFF, 0x00, # 0-255 for analog sticks 39 | 0x46, 0xFF, 0x00, 40 | 0x09, 0x30, # X (left X) 41 | 0x09, 0x31, # Y (left Y) 42 | 0x09, 0x32, # Z (right X) 43 | 0x09, 0x35, # Rz (right Y) 44 | 0x75, 0x08, # 1 byte per report field 45 | # 4 report fields (left X, left Y, right X, right Y) 46 | 0x95, 0x04, 47 | 0x81, 0x02, # Variable input 48 | 0x75, 0x08, # 1 byte per report field 49 | 0x95, 0x01, # 1 report field 50 | 0x81, 0x01, # Array input 51 | 0xC0, 52 | )) 53 | 54 | HoriPadS = usb_hid.Device( 55 | report_descriptor=_HORIPAD_S_DESCRIPTOR, 56 | usage_page=0x01, 57 | usage=0x05, 58 | report_ids=(0,), 59 | in_report_lengths=(8,), 60 | out_report_lengths=(0,), 61 | ) 62 | 63 | class Device_Horipad_S(device.Device): 64 | def __init__(self): 65 | super().__init__() 66 | 67 | def init_device(self): 68 | try: 69 | import supervisor 70 | supervisor.set_usb_identification("HORI CO.,LTD.","HORIPAD S",0x0F0D,0x00C1) 71 | except: 72 | pass 73 | usb_hid.enable((HoriPadS,)) 74 | 75 | def find_device(self): 76 | return find_device( 77 | usb_hid.devices, usage_page=0x01, usage=0x05) -------------------------------------------------------------------------------- /video-server-python/datatype/device.py: -------------------------------------------------------------------------------- 1 | import platform 2 | class VideoDevice(object): 3 | def __init__(self,name:str,width:int,height:int,fps:int,index:int=0,pix_fmt:str = None,vcodec:str = None): 4 | self._name = name 5 | self._width = width 6 | self._height = height 7 | self._fps = fps 8 | self._pix_fmt = pix_fmt 9 | self._vcodec = vcodec 10 | self._index = index 11 | self._sys_str = platform.system() 12 | 13 | @property 14 | def name(self): 15 | return self._name 16 | @property 17 | def ffmepg_name(self): 18 | if self._sys_str == "Windows": 19 | return "video={}".format(self._name) 20 | else: 21 | return self._name 22 | @property 23 | def width(self): 24 | return self._width 25 | @property 26 | def height(self): 27 | return self._height 28 | @property 29 | def format(self): 30 | _format = '' 31 | if self._sys_str == "Windows": 32 | _format = 'dshow' 33 | elif self._sys_str == "Darwin": 34 | _format = 'avfoundation' 35 | else: 36 | _format = 'video4linux2' 37 | return _format 38 | @property 39 | def fps(self): 40 | return self._fps 41 | @property 42 | def index(self): 43 | return self._index 44 | @property 45 | def pix_fmt(self): 46 | return self._pix_fmt 47 | @property 48 | def vcodec(self): 49 | return self._vcodec 50 | 51 | class AudioDevice(object): 52 | def __init__(self,name:str,sample_rate:int,channels:int): 53 | self._name = name 54 | self._sample_rate = sample_rate 55 | self._channels = channels 56 | self._sys_str = platform.system() 57 | 58 | @property 59 | def name(self): 60 | return self._name 61 | 62 | @property 63 | def ffmepg_name(self): 64 | if self._sys_str == "Windows": 65 | return "audio=数字音频接口 ({})".format(self._name) 66 | else: 67 | return ":{}".format(self._name) 68 | 69 | @property 70 | def sample_rate(self): 71 | return self._sample_rate 72 | 73 | @property 74 | def channels(self): 75 | return self._channels 76 | 77 | @property 78 | def format(self): 79 | _format = '' 80 | sys_str = platform.system() 81 | if sys_str == "Windows": 82 | _format = 'dshow' 83 | elif sys_str == "Darwin": 84 | _format = 'avfoundation' 85 | else: 86 | _format = 'alsa' 87 | return _format 88 | 89 | class JoystickDevice(object): 90 | def __init__(self,host:str,port:int): 91 | self._host = host 92 | self._port = port 93 | 94 | @property 95 | def host(self): 96 | return self._host 97 | 98 | @property 99 | def port(self): 100 | return self._port 101 | -------------------------------------------------------------------------------- /video-server-python/capture/video.py: -------------------------------------------------------------------------------- 1 | import datatype.device as device 2 | import cv2 3 | import time 4 | from PySide6.QtMultimedia import (QMediaDevices) 5 | class Video(): 6 | def __init__(self): 7 | pass 8 | 9 | # def _start_ffmpeg(self,dev:device.VideoDevice,display_width,display_height,display_fps): 10 | # if dev == None: 11 | # return 12 | 13 | # input_paras = dict[str,Any]() 14 | # input_paras["framerate"] = dev.fps 15 | # input_paras["s"] = "{}*{}".format(dev.width,dev.height) 16 | # input_paras["format"] = dev.format 17 | 18 | # if dev.pix_fmt != None: 19 | # input_paras["pix_fmt"] = dev.pix_fmt 20 | # if dev.vcodec != None: 21 | # input_paras["vcodec"] = dev.vcodec 22 | # sys.stdout = open(os.devnull, 'w') 23 | # sys.stderr = open(os.devnull, 'w') 24 | # process = ( 25 | # ffmpeg 26 | # .input("{}".format(dev.ffmepg_name),**input_paras) 27 | # .output( 28 | # "pipe:", 29 | # f='rawvideo', 30 | # pix_fmt='bgr24', 31 | # r=display_fps, 32 | # s="{}*{}".format(display_width,display_height), 33 | # ) 34 | # .run_async(pipe_stdout=True,) 35 | # # .run_async(quiet=True,pipe_stdout=True) 36 | # ) 37 | # return process 38 | 39 | def run(self,pipe,dev:device.VideoDevice,display_width,display_height,display_fps): 40 | if dev == None: 41 | return 42 | self.capture(pipe,dev,display_width,display_height,display_fps) 43 | # process = self._start_ffmpeg(dev,display_width,display_height,display_fps) 44 | # while True: 45 | # frame_bytes = process.stdout.read(display_width * display_height * 3) 46 | # if not frame_bytes: 47 | # break 48 | # if pipe.writable: 49 | # pipe.send(frame_bytes) 50 | # else: 51 | # break 52 | # process.terminate() 53 | 54 | def capture(self,pipe,dev:device.VideoDevice,display_width,display_height,display_fps:int): 55 | available_cameras = QMediaDevices.videoInputs() 56 | cap = cv2.VideoCapture(dev.index,cv2.CAP_DSHOW) 57 | cap.set(cv2.CAP_PROP_FRAME_WIDTH,dev.width) 58 | cap.set(cv2.CAP_PROP_FRAME_HEIGHT,dev.height) 59 | cap.set(cv2.CAP_PROP_POS_FRAMES,dev.fps) 60 | cap.set(cv2.CAP_PROP_FOURCC,cv2.VideoWriter_fourcc(*'MJPG')) 61 | if display_fps <= 0: 62 | display_fps = dev.fps 63 | last_cap_monotonic = time.monotonic() 64 | min_interval = 1 / display_fps 65 | while cap.isOpened(): 66 | ret, frame = cap.read() 67 | now = time.monotonic() 68 | if now - last_cap_monotonic >= min_interval: 69 | last_cap_monotonic = now 70 | frame = cv2.resize(frame, (display_width, display_height)) 71 | if pipe.writable: 72 | pipe.send(frame.tobytes()) 73 | cap.release() 74 | cv2.destroyAllWindows() -------------------------------------------------------------------------------- /src/lib/asyncio/event.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019-2020 Damien P. George 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # 5 | # MicroPython uasyncio module 6 | # MIT license; Copyright (c) 2019-2020 Damien P. George 7 | # 8 | # This code comes from MicroPython, and has not been run through black or pylint there. 9 | # Altering these files significantly would make merging difficult, so we will not use 10 | # pylint or black. 11 | # pylint: skip-file 12 | # fmt: off 13 | """ 14 | Events 15 | ====== 16 | """ 17 | 18 | from . import core 19 | 20 | # Event class for primitive events that can be waited on, set, and cleared 21 | class Event: 22 | """Create a new event which can be used to synchronize tasks. Events 23 | start in the cleared state. 24 | """ 25 | 26 | def __init__(self): 27 | self.state = False # False=unset; True=set 28 | self.waiting = ( 29 | core.TaskQueue() 30 | ) # Queue of Tasks waiting on completion of this event 31 | 32 | def is_set(self): 33 | """Returns ``True`` if the event is set, ``False`` otherwise.""" 34 | 35 | return self.state 36 | 37 | def set(self): 38 | """Set the event. Any tasks waiting on the event will be scheduled to run. 39 | """ 40 | 41 | # Event becomes set, schedule any tasks waiting on it 42 | # Note: This must not be called from anything except the thread running 43 | # the asyncio loop (i.e. neither hard or soft IRQ, or a different thread). 44 | while self.waiting.peek(): 45 | core._task_queue.push_head(self.waiting.pop_head()) 46 | self.state = True 47 | 48 | def clear(self): 49 | """Clear the event.""" 50 | 51 | self.state = False 52 | 53 | async def wait(self): 54 | """Wait for the event to be set. If the event is already set then it returns 55 | immediately. 56 | 57 | This is a coroutine. 58 | """ 59 | 60 | if not self.state: 61 | # Event not set, put the calling task on the event's waiting queue 62 | self.waiting.push_head(core.cur_task) 63 | # Set calling task's data to the event's queue so it can be removed if needed 64 | core.cur_task.data = self.waiting 65 | await core._never() 66 | return True 67 | 68 | 69 | # MicroPython-extension: This can be set from outside the asyncio event loop, 70 | # such as other threads, IRQs or scheduler context. Implementation is a stream 71 | # that asyncio will poll until a flag is set. 72 | # Note: Unlike Event, this is self-clearing. 73 | try: 74 | import uio 75 | 76 | class ThreadSafeFlag(uio.IOBase): 77 | def __init__(self): 78 | self._flag = 0 79 | 80 | def ioctl(self, req, flags): 81 | if req == 3: # MP_STREAM_POLL 82 | return self._flag * flags 83 | return None 84 | 85 | def set(self): 86 | self._flag = 1 87 | 88 | async def wait(self): 89 | if not self._flag: 90 | yield core._io_queue.queue_read(self) 91 | self._flag = 0 92 | 93 | 94 | except ImportError: 95 | pass 96 | -------------------------------------------------------------------------------- /src/lib/asyncio/lock.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019-2020 Damien P. George 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # 5 | # MicroPython uasyncio module 6 | # MIT license; Copyright (c) 2019-2020 Damien P. George 7 | # 8 | # This code comes from MicroPython, and has not been run through black or pylint there. 9 | # Altering these files significantly would make merging difficult, so we will not use 10 | # pylint or black. 11 | # pylint: skip-file 12 | # fmt: off 13 | """ 14 | Locks 15 | ===== 16 | """ 17 | 18 | from . import core 19 | 20 | # Lock class for primitive mutex capability 21 | class Lock: 22 | """Create a new lock which can be used to coordinate tasks. Locks start in 23 | the unlocked state. 24 | 25 | In addition to the methods below, locks can be used in an ``async with`` 26 | statement. 27 | """ 28 | 29 | def __init__(self): 30 | # The state can take the following values: 31 | # - 0: unlocked 32 | # - 1: locked 33 | # - : unlocked but this task has been scheduled to acquire the lock next 34 | self.state = 0 35 | # Queue of Tasks waiting to acquire this Lock 36 | self.waiting = core.TaskQueue() 37 | 38 | def locked(self): 39 | """Returns ``True`` if the lock is locked, otherwise ``False``.""" 40 | 41 | return self.state == 1 42 | 43 | def release(self): 44 | """Release the lock. If any tasks are waiting on the lock then the next 45 | one in the queue is scheduled to run and the lock remains locked. Otherwise, 46 | no tasks are waiting and the lock becomes unlocked. 47 | """ 48 | 49 | if self.state != 1: 50 | raise RuntimeError("Lock not acquired") 51 | if self.waiting.peek(): 52 | # Task(s) waiting on lock, schedule next Task 53 | self.state = self.waiting.pop_head() 54 | core._task_queue.push_head(self.state) 55 | else: 56 | # No Task waiting so unlock 57 | self.state = 0 58 | 59 | async def acquire(self): 60 | """Wait for the lock to be in the unlocked state and then lock it in an 61 | atomic way. Only one task can acquire the lock at any one time. 62 | 63 | This is a coroutine. 64 | """ 65 | 66 | if self.state != 0: 67 | # Lock unavailable, put the calling Task on the waiting queue 68 | self.waiting.push_head(core.cur_task) 69 | # Set calling task's data to the lock's queue so it can be removed if needed 70 | core.cur_task.data = self.waiting 71 | try: 72 | await core._never() 73 | except core.CancelledError as er: 74 | if self.state == core.cur_task: 75 | # Cancelled while pending on resume, schedule next waiting Task 76 | self.state = 1 77 | self.release() 78 | raise er 79 | # Lock available, set it as locked 80 | self.state = 1 81 | return True 82 | 83 | async def __aenter__(self): 84 | return await self.acquire() 85 | 86 | async def __aexit__(self, exc_type, exc, tb): 87 | return self.release() 88 | -------------------------------------------------------------------------------- /src/resources/macros/pokemon/swsh/common.m: -------------------------------------------------------------------------------- 1 | --打开宝可梦剑盾-- 2 | 3 | A:0.1 4 | 1.5 5 | A:0.1 6 | 15 7 | {5}?secondary 8 | A:0.1 9 | 1 10 | A:0.1 11 | 1 12 | A:0.1 13 | 7 14 | 15 | --重启宝可梦剑盾-- 16 | 17 | [common.close_game] 18 | [open_game] 19 | 20 | 21 | --保存进度-- 22 | 23 | X:0.1 24 | 1.5 25 | R:0.1 26 | 1.5 27 | A:0.1 28 | 4 29 | 30 | --开启团战 获取瓦特等待(脱机)-- 31 | 32 | A:0.1 33 | 0.8 34 | A:0.1 35 | 0.8 36 | 37 | --开启团战 获取瓦特等待(联机)-- 38 | 39 | A:0.1 40 | 1 41 | 0.8 42 | A:0.1 43 | 0.8 44 | 2.5 45 | 46 | 47 | --开启团战(脱机)-- 48 | 49 | [goto_raid_watts_wait_offline] 50 | 2.5 51 | 52 | --开启团战(联机)-- 53 | 54 | [goto_raid_watts_wait_online] 55 | 7 56 | 2.5 57 | 58 | 59 | --设置团战密码22223333-- 60 | 61 | Plus:0.1 62 | 1 63 | LStick@127,0 64 | 0.5 65 | A:0.1 66 | 0.4 67 | A:0.1 68 | 0.4 69 | A:0.1 70 | 0.4 71 | A:0.1 72 | 0.4 73 | LStick@127,0 74 | 0.5 75 | A:0.1 76 | 0.4 77 | A:0.1 78 | 0.4 79 | A:0.1 80 | 0.4 81 | A:0.1 82 | 0.4 83 | Plus:0.1 84 | 1 85 | A:0.1 86 | 1 87 | 88 | 89 | --开启团战并等待其他玩家加入-- 90 | 91 | A:0.1 92 | 10 93 | LStick@0,-128:0.1 94 | 90 95 | 96 | 97 | --进行团战-- 98 | 99 | { 100 | A:0.1 101 | 1 102 | }*20 103 | 104 | 105 | --联机->脱机-- 106 | 107 | Y:0.1 108 | 1 109 | Plus:0.1 110 | 0.5 111 | A:0.1 112 | 5 113 | B:0.1 114 | 0.5 115 | B:0.1 116 | 1 117 | 118 | --脱机->联机-- 119 | 120 | Y:0.1 121 | 1 122 | Plus:0.1 123 | 30 124 | A:0.1 125 | 1 126 | B:0.1 127 | 2 128 | 129 | 130 | --切换网络-- 131 | 132 | Y:0.1 133 | 1 134 | Plus:0.1 135 | 60 136 | A:0.1 137 | 1 138 | Plus:0.1 139 | 0.5 140 | A:0.1 141 | 5 142 | B:0.1 143 | 0.5 144 | B:0.1 145 | 1.5 146 | 147 | --进入时间设置界面-- 148 | 149 | # 进入设置界面 150 | LStick@0,127:0.05 151 | 0.05 152 | LStick@127,0:0.05 153 | 0.05 154 | LStick@127,0:0.05 155 | 0.05 156 | LStick@127,0:0.05 157 | 0.05 158 | LStick@127,0:0.05 159 | 0.05 160 | LStick@127,0:0.05 161 | 0.05 162 | A:0.05 163 | 1 164 | # 进入时间设置界面 165 | { 166 | LStick@127,0:0.05 167 | 0.05 168 | }*14 169 | 0.05 170 | A:0.05 171 | 1 172 | LStick@0,127:0.05 173 | 0.05 174 | LStick@0,127:0.05 175 | 0.05 176 | LStick@0,127:0.05 177 | 0.05 178 | LStick@0,127:0.05 179 | 0.05 180 | A:0.05 181 | 1 182 | LStick@0,127:0.05 183 | 0.05 184 | LStick@0,127:0.05 185 | 0.05 186 | 187 | 188 | --首次修改日期-- 189 | 190 | # 进入设置界面 191 | A:0.05 192 | 0.5 193 | # 首次修改日期 194 | A:0.05 195 | 0.05 196 | A:0.05 197 | 0.05 198 | LStick@0,-128:0.05 199 | 0.05 200 | A:0.05 201 | 0.05 202 | A:0.05 203 | 0.05 204 | A:0.05 205 | 0.05 206 | A:0.05 207 | 0.12 208 | 209 | --后续修改日期-- 210 | 211 | A:0.05 212 | 0.12 213 | LStick@-128,0:0.05 214 | 0.05 215 | LStick@-128,0:0.05 216 | 0.05 217 | LStick@-128,0:0.05 218 | 0.05 219 | LStick@0,-128:0.05 220 | 0.05 221 | A:0.05 222 | 0.05 223 | A:0.05 224 | 0.05 225 | A:0.05 226 | 0.05 227 | A:0.05 228 | 0.12 229 | 230 | 231 | 232 | 7.8 233 | LStick@0,127:0.1 234 | 0.1 235 | LStick@0,127:0.1 236 | 2.1 237 | LStick@0,127:0.1 238 | 0.5 239 | A:0.1 240 | 1.5 241 | A:0.1 242 | 0.8 243 | LStick@0,127:0.1 244 | 0.3 245 | A:0.1 246 | 3.3 -------------------------------------------------------------------------------- /video-server-python/main.py: -------------------------------------------------------------------------------- 1 | import capture 2 | import controller 3 | import datatype.device as device 4 | import recognize 5 | import ui 6 | import sys 7 | import multiprocessing 8 | import time 9 | 10 | _Camera_Name = "USB Video" 11 | _Camera_Width = 1280 12 | _Camera_Height = 720 13 | _Camera_FPS = 60 14 | 15 | _Audio_Device_Name = "数字音频接口 ({})".format("USB Digital Audio") 16 | 17 | _Display_Width = 960 18 | _Display_Height = 540 19 | _Display_FPS = 60 20 | _Recognize_FPS = 30 21 | 22 | # _Camera_Name = "FaceTime高清摄像头(内建)" 23 | # _Camera_FPS = 30 24 | # _Audio_Device_Name = "内建麦克风" 25 | 26 | # _Camera_Name = "Game Capture HD60 S+" 27 | # _Audio_Device_Name = "Digital Audio Interface ({})".format( "Game Capture HD60 S+") 28 | 29 | def main(): 30 | dev_video = device.VideoDevice(name=_Camera_Name,width=_Camera_Width,height=_Camera_Height,fps=_Camera_FPS,index=0,pix_fmt=None) 31 | # dev_video = device.VideoDevice(name=_Camera_Name,width=_Camera_Width,height=_Camera_Height,fps=_Camera_FPS,index=0,pix_fmt=None,vcodec="mjpeg") 32 | # dev_video = device.VideoDevice(name=_Camera_Name,width=_Camera_Width,height=_Camera_Height,fps=_Camera_FPS,index=1,pix_fmt="bgr0") 33 | dev_audio = device.AudioDevice(name=_Audio_Device_Name,sample_rate=44100,channels=2) 34 | dev_joystick = device.JoystickDevice(host="192.168.50.120",port=5000) 35 | main_video_frame,capture_video_frame = multiprocessing.Pipe(False) 36 | ui_display_video_frame = multiprocessing.Queue() 37 | opencv_processed_video_frame = multiprocessing.Queue() 38 | recognize_video_frame = multiprocessing.Queue(1) 39 | frame_queues = (recognize_video_frame,ui_display_video_frame,opencv_processed_video_frame,) 40 | controller_action_queue = multiprocessing.Queue() 41 | opencv_processed_control_queue = multiprocessing.Queue() 42 | control_queues=(opencv_processed_control_queue,controller_action_queue,) 43 | controller_process = multiprocessing.Process(target=controller.run, args=(dev_joystick,control_queues,)) 44 | controller_process.start() 45 | ui_process = multiprocessing.Process(target=ui.run, args=(frame_queues,control_queues,dev_audio,_Display_Width,_Display_Height)) 46 | ui_process.start() 47 | recognize_process = multiprocessing.Process(target=recognize.run, args=(frame_queues,control_queues,_Display_Width,_Display_Height,_Recognize_FPS,)) 48 | recognize_process.start() 49 | video_process = multiprocessing.Process(target=capture.capture_video, args=(capture_video_frame,dev_video,_Display_Width,_Display_Height,_Display_FPS,)) 50 | video_process.start() 51 | try: 52 | while True: 53 | if not ui_process.is_alive(): 54 | break 55 | video_frame = main_video_frame.recv() 56 | try: 57 | ui_display_video_frame.put(video_frame,False,0) 58 | except: 59 | pass 60 | try: 61 | recognize_video_frame.put(video_frame,False,0) 62 | except: 63 | pass 64 | time.sleep(0.001) 65 | sys.exit(0) 66 | except: 67 | pass 68 | finally: 69 | controller_process.kill() 70 | video_process.kill() 71 | recognize_process.kill() 72 | ui_process.kill() 73 | 74 | if __name__ == "__main__": 75 | multiprocessing.freeze_support() 76 | main() 77 | 78 | -------------------------------------------------------------------------------- /src/lib/adafruit_httpserver/headers.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2022 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | """ 5 | `adafruit_httpserver.headers` 6 | ==================================================== 7 | * Author(s): Michał Pokusa 8 | """ 9 | 10 | try: 11 | from typing import Dict, Tuple 12 | except ImportError: 13 | pass 14 | 15 | 16 | class Headers: 17 | """ 18 | A dict-like class for storing HTTP headers. 19 | 20 | Allows access to headers using **case insensitive** names. 21 | 22 | Does **not** implement all dict methods. 23 | 24 | Examples:: 25 | 26 | headers = Headers({"Content-Type": "text/html", "Content-Length": "1024"}) 27 | 28 | len(headers) 29 | # 2 30 | 31 | headers.setdefault("Access-Control-Allow-Origin", "*") 32 | headers["Access-Control-Allow-Origin"] 33 | # '*' 34 | 35 | headers["Content-Length"] 36 | # '1024' 37 | 38 | headers["content-type"] 39 | # 'text/html' 40 | 41 | headers["User-Agent"] 42 | # KeyError: User-Agent 43 | 44 | "CONTENT-TYPE" in headers 45 | # True 46 | """ 47 | 48 | _storage: Dict[str, Tuple[str, str]] 49 | 50 | def __init__(self, headers: Dict[str, str] = None) -> None: 51 | headers = headers or {} 52 | 53 | self._storage = {key.lower(): [key, value] for key, value in headers.items()} 54 | 55 | def get(self, name: str, default: str = None): 56 | """Returns the value for the given header name, or default if not found.""" 57 | return self._storage.get(name.lower(), [None, default])[1] 58 | 59 | def setdefault(self, name: str, default: str = None): 60 | """Sets the value for the given header name if it does not exist.""" 61 | return self._storage.setdefault(name.lower(), [name, default])[1] 62 | 63 | def items(self): 64 | """Returns a list of (name, value) tuples.""" 65 | return dict(self._storage.values()).items() 66 | 67 | def keys(self): 68 | """Returns a list of header names.""" 69 | return dict(self._storage.values()).keys() 70 | 71 | def values(self): 72 | """Returns a list of header values.""" 73 | return dict(self._storage.values()).values() 74 | 75 | def update(self, headers: Dict[str, str]): 76 | """Updates the headers with the given dict.""" 77 | return self._storage.update( 78 | {key.lower(): [key, value] for key, value in headers.items()} 79 | ) 80 | 81 | def copy(self): 82 | """Returns a copy of the headers.""" 83 | return Headers(dict(self._storage.values())) 84 | 85 | def __getitem__(self, name: str): 86 | return self._storage[name.lower()][1] 87 | 88 | def __setitem__(self, name: str, value: str): 89 | self._storage[name.lower()] = [name, value] 90 | 91 | def __delitem__(self, name: str): 92 | del self._storage[name.lower()] 93 | 94 | def __iter__(self): 95 | return iter(dict(self._storage.values())) 96 | 97 | def __len__(self): 98 | return len(self._storage) 99 | 100 | def __contains__(self, key: str): 101 | return key.lower() in self._storage.keys() 102 | 103 | def __repr__(self): 104 | return f"{self.__class__.__name__}({dict(self._storage.values())})" 105 | -------------------------------------------------------------------------------- /SCRIPTS.md: -------------------------------------------------------------------------------- 1 | ## 脚本语法规则 2 | 3 | ### 位置 4 | 5 | 脚本文件:ESP32目录/resources/macros/......(.m文件) 6 | 7 | ### 语法 8 | 9 | ##### 基本语法 10 | 11 | ``` 12 | A:0.1 13 | ``` 14 | 15 | 按下A,0.1秒后抬起 16 | 17 | ``` 18 | 1 19 | ``` 20 | 21 | 抬起所有按键,等待1秒 22 | 23 | ``` 24 | A|B|HOME:0.5 25 | ``` 26 | 27 | 同时按下A、B、Home键,0.5秒后抬起 28 | 29 | ##### 按键对应关系 30 | 31 | Y/X/A/B键对应:Y, X, A, B 32 | 33 | L/ZL/R/ZR键对应: L/ZL/R/ZR 34 | 35 | -/+/Capture/Home键对应:MINUS, PLUS, CAPTURE, HOME 36 | 37 | 十字键方向对应:TOP, BOTTOM, LEFT, RIGHT, TOPLEFT, TOPRIGHT, BOTTOMLEFT, BOTTOMRIGHT , CENTER (若同时设置多个十字键防线,会变为Center) 38 | 39 | 左/右摇杆按下:LPRESS, RPRESS 40 | 41 | 左右摇杆方向调整:LSTICK@+100,-20 RSTICK@-128,127(第一个数字为负为左,正为右;第二个数字为纵坐标,负为上,正为下。取值范围均为:-128-127) 42 | 43 | ##### 进阶语法 44 | 45 | ``` 46 | --返回游戏界面(非Home页面)-- 47 | 48 | [common.return_home] 49 | A:0.1 50 | 1.5 51 | ``` 52 | 53 | --: 以此开头的行为注释(必须在行首使用) 54 | 55 | \: 宏的区块开始标识,并标记区块名称,可通过[目录.文件.区块名称]调用,同时还可以设置web接口调用标题与参数 56 | 57 | \[common.return_home\]: 区块调用,调用common.m文件return_home区块 58 | 59 | ##### 实际案例 60 | 61 | ``` 62 | --雷吉洛克/雷吉斯奇鲁-- 63 | 64 | { 65 | [common.wakeup_joystick] 66 | [pokemon.swsh.common.restart_game] 67 | body: 68 | A:0.1 69 | 1 70 | A:0.1 71 | 1.2 72 | A:0.1 73 | 6.5 74 | [pokemon.swsh.common.battle_check_shiny] 75 | A:0.1 76 | 0.5 77 | # 走到左上角墙角 78 | LStick@-127,0:1.8 79 | LStick@9,0:0.05 80 | 0.3 81 | LStick@0,-127:0.8 82 | LStick@0,9:0.05 83 | 0.2 84 | LStick@0,-127:0.3 85 | LStick@9,0:0.05 86 | 0.3 87 | LStick@-127,0:0.3 88 | LStick@9,0:0.05 89 | 0.7 90 | # 踩灯 第3排 91 | LStick@0,127:1.71 92 | LStick@0,-9:0.05 93 | 0.3 94 | LStick@127,0:2.5 95 | LStick@-9,0:0.05 96 | 0.7 97 | # 踩灯 第2排 98 | LStick@0,-127:0.52 99 | LStick@0,9:0.05 100 | 0.3 101 | LStick@-127,0:2.1 102 | LStick@9,0:0.05 103 | 0.7 104 | # 踩灯 第1排 105 | LStick@0,-127:0.52 106 | LStick@0,9:0.05 107 | 0.3 108 | LStick@127,0:2.7 109 | 3 110 | # 等待 并走到石像前 111 | A:0.1 112 | 1 113 | LStick@-127,0:0.5 114 | 0.2 115 | LStick@0,-127:1 116 | 0.2 117 | } 118 | ``` 119 | 120 | - 标题行: 121 | 122 | - 以--分割标题与参数部分,支持多参数。 123 | 124 | - 标题部分以|分割,分为3部分。第一部分为区块名称,脚本内部调用使用。第二部分为中文名称,web管理时只能看到中文名称,没有中文名称的区块不展示。第三部分为默认循环次数,web管理界面选择这个脚本时,会把循环次数首先设置为这个默认值。 125 | 126 | - 参数部分以|分割,分为3部分。第一部分为参数名称,第二部分为中文名称,同样是web管理时展示使用,第三部分为参数默认值,也是web管理时页面选择脚本时,该参数会默认修改为设定的值 127 | 128 | - "body:" : 标记重复运行时循环起始位置,body:标记前一般为前置设置 129 | 130 | - /# : 注释行,无实际意义(必须在行首使用) 131 | 132 | - {}: 匿名区块,单独使用无实际意义,后可跟?或*语句 133 | 134 | ``` 135 | {5}?secondary 136 | ``` 137 | 138 | - ?secondary: 根据配置中的secondary标识,设置{}内部分脚本是否执行 139 | 140 | ``` 141 | {5}*5 142 | ``` 143 | 144 | - \*5: {}内部分脚本重复执行5次 145 | 146 | - ?与\*运算支持表达式,如: 147 | 148 | ``` 149 | # 暂停五秒,并循环3次(2+1) 150 | 151 | {5}*(1+2) 152 | ``` 153 | 154 | ``` 155 | # 暂停五秒,因为3>2为True 156 | 157 | {5}?(3>2) 158 | ``` 159 | 160 | ``` 161 | # 暂停五秒,并循环a+b次,小数部分截取(虽然支持变量,但是由于变量本质上都界面发送,都是字符串,所以要先把变量转化为数值才可以计算) 162 | 163 | {5}*(int(float(a)+float(b)) 164 | ``` 165 | 166 | ``` 167 | # 当A为True,B为False的时候暂停5秒(还是由于变量本质都是字符串的原因,要以字符串形式判断,由于bool类型的强制类型转换比较特殊,建议直接判断字符串内容即可,注意字符串大小写) 168 | 169 | {5}?(a=="True" and b=="False") 170 | ``` 171 | -------------------------------------------------------------------------------- /src/http_server.py: -------------------------------------------------------------------------------- 1 | import socketpool 2 | import wifi 3 | import customize.wifi_connect as wifi_connect 4 | import customize.device_info as device_info 5 | import macros 6 | import asyncio 7 | from adafruit_httpserver.response import Response,FileResponse 8 | from adafruit_httpserver.server import Server 9 | from adafruit_httpserver.mime_types import MIMETypes 10 | from adafruit_httpserver.request import Request 11 | 12 | MIMETypes.configure( 13 | default_to="text/plain", 14 | keep_for=[".html", ".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".ico"], 15 | ) 16 | 17 | _pool = socketpool.SocketPool(wifi.radio) 18 | _server = Server(_pool,"/") 19 | 20 | 21 | async def serve(): 22 | global _server 23 | HOST = str(wifi_connect.ip_address()) 24 | print(HOST) 25 | _server.start(HOST) 26 | 27 | while True: 28 | try: 29 | _server.poll() 30 | await asyncio.sleep_ms(50) 31 | except OSError: 32 | continue 33 | 34 | 35 | @_server.route("/cpu", "GET") 36 | def ram(request: Request): 37 | return Response(request, content_type="text/plain;charset=utf-8",body="CPU温度: {: .2f}".format(device_info.cpu_temperature())) 38 | 39 | @_server.route("/ram", "GET") 40 | def ram(request: Request): 41 | return Response(request, content_type="text/plain;charset=utf-8",body="剩余内存: {: .2f}KB".format(device_info.mem_free())) 42 | 43 | @_server.route("/rom", "GET") 44 | def rom(request: Request): 45 | rom = device_info.get_rom_info() 46 | return Response(request, content_type="text/plain;charset=utf-8",body="剩余存储空间:{:.2f}MB/{:.2f}MB".format(rom[0], rom[1])) 47 | 48 | 49 | @_server.route("/status", "GET") 50 | def status(request: Request): 51 | txt = "" 52 | txt += "{}\n\n".format(macros.status_info()) 53 | txt += "CPU温度: {: .2f}\n".format(device_info.cpu_temperature()) 54 | txt += "剩余内存: {: .2f}KB\n".format(device_info.mem_free()) 55 | rom = device_info.get_rom_info() 56 | txt += "剩余存储空间:{:.2f}MB/{:.2f}MB\n".format(rom[0], rom[1]) 57 | return Response(request, content_type="text/plain;charset=utf-8",body=txt) 58 | 59 | 60 | @_server.route("/macro/current", "GET") 61 | def macro_current(request: Request): 62 | return Response(request, content_type="text/plain;charset=utf-8",body=macros.current_info()) 63 | 64 | 65 | @_server.route("/macro/result", "GET") 66 | def macro_result(request: Request): 67 | return Response(request, content_type="text/plain;charset=utf-8",body=macros.result_info()) 68 | 69 | 70 | @_server.route("/macro/stop", "GET") 71 | def macro_stop(request: Request): 72 | macros.macro_stop() 73 | return Response(request, content_type="text/plain;charset=utf-8",body="Done") 74 | 75 | @_server.route("/macro/published", "GET") 76 | def marcos(request: Request): 77 | body = macros.published() 78 | return Response(request, content_type="text/plain;charset=utf-8",body=body) 79 | 80 | @_server.route("/macro/start", "POST") 81 | def macro_start(request: Request): 82 | raw_text = request.raw_request.decode("utf8") 83 | splits = raw_text.split("\n") 84 | cmd = splits[len(splits) - 1] 85 | ret = macros.add_joystick_task(cmd) 86 | return Response(request, content_type="text/plain;charset=utf-8",body=ret) 87 | 88 | @_server.route("/") 89 | def index_root(request: Request): 90 | return FileResponse(request, filename='index.html', root_path='/web') 91 | 92 | @_server.route("/index.html") 93 | def index(request: Request): 94 | return FileResponse(request, filename='index.html', root_path='/web') 95 | 96 | @_server.route("/jquery-3.6.1.min.js") 97 | def js1(request: Request): 98 | return FileResponse(request, filename='jquery-3.6.1.min.js', root_path='/web') 99 | -------------------------------------------------------------------------------- /src/lib/asyncio/funcs.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019-2020 Damien P. George 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # 5 | # MicroPython uasyncio module 6 | # MIT license; Copyright (c) 2019-2020 Damien P. George 7 | # 8 | # This code comes from MicroPython, and has not been run through black or pylint there. 9 | # Altering these files significantly would make merging difficult, so we will not use 10 | # pylint or black. 11 | # pylint: skip-file 12 | # fmt: off 13 | """ 14 | Functions 15 | ========= 16 | """ 17 | 18 | 19 | from . import core 20 | 21 | 22 | async def wait_for(aw, timeout, sleep=core.sleep): 23 | """Wait for the *aw* awaitable to complete, but cancel if it takes longer 24 | than *timeout* seconds. If *aw* is not a task then a task will be created 25 | from it. 26 | 27 | If a timeout occurs, it cancels the task and raises ``asyncio.TimeoutError``: 28 | this should be trapped by the caller. 29 | 30 | Returns the return value of *aw*. 31 | 32 | This is a coroutine. 33 | """ 34 | 35 | aw = core._promote_to_task(aw) 36 | if timeout is None: 37 | return await aw 38 | 39 | async def runner(waiter, aw): 40 | nonlocal status, result 41 | try: 42 | result = await aw 43 | s = True 44 | except BaseException as er: 45 | s = er 46 | if status is None: 47 | # The waiter is still waiting, set status for it and cancel it. 48 | status = s 49 | waiter.cancel() 50 | 51 | # Run aw in a separate runner task that manages its exceptions. 52 | status = None 53 | result = None 54 | runner_task = core.create_task(runner(core.cur_task, aw)) 55 | 56 | try: 57 | # Wait for the timeout to elapse. 58 | await sleep(timeout) 59 | except core.CancelledError as er: 60 | if status is True: 61 | # aw completed successfully and cancelled the sleep, so return aw's result. 62 | return result 63 | elif status is None: 64 | # This wait_for was cancelled externally, so cancel aw and re-raise. 65 | status = True 66 | runner_task.cancel() 67 | raise er 68 | else: 69 | # aw raised an exception, propagate it out to the caller. 70 | raise status 71 | 72 | # The sleep finished before aw, so cancel aw and raise TimeoutError. 73 | status = True 74 | runner_task.cancel() 75 | await runner_task 76 | raise core.TimeoutError 77 | 78 | 79 | def wait_for_ms(aw, timeout): 80 | """Similar to `wait_for` but *timeout* is an integer in milliseconds. 81 | 82 | This is a coroutine, and a MicroPython extension. 83 | """ 84 | 85 | return wait_for(aw, timeout, core.sleep_ms) 86 | 87 | 88 | async def gather(*aws, return_exceptions=False): 89 | """Run all *aws* awaitables concurrently. Any *aws* that are not tasks 90 | are promoted to tasks. 91 | 92 | Returns a list of return values of all *aws* 93 | 94 | This is a coroutine. 95 | """ 96 | 97 | ts = [core._promote_to_task(aw) for aw in aws] 98 | for i in range(len(ts)): 99 | try: 100 | # TODO handle cancel of gather itself 101 | # if ts[i].coro: 102 | # iter(ts[i]).waiting.push_head(cur_task) 103 | # try: 104 | # yield 105 | # except CancelledError as er: 106 | # # cancel all waiting tasks 107 | # raise er 108 | ts[i] = await ts[i] 109 | except (core.CancelledError, Exception) as er: 110 | if return_exceptions: 111 | ts[i] = er 112 | else: 113 | raise er 114 | return ts 115 | -------------------------------------------------------------------------------- /src/lib/adafruit_hid/consumer_control.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2018 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid.consumer_control.ConsumerControl` 7 | ==================================================== 8 | 9 | * Author(s): Dan Halbert 10 | """ 11 | 12 | import sys 13 | 14 | if sys.implementation.version[0] < 3: 15 | raise ImportError( 16 | "{0} is not supported in CircuitPython 2.x or lower".format(__name__) 17 | ) 18 | 19 | # pylint: disable=wrong-import-position 20 | import struct 21 | import time 22 | from . import find_device 23 | 24 | try: 25 | from typing import Sequence 26 | import usb_hid 27 | except ImportError: 28 | pass 29 | 30 | 31 | class ConsumerControl: 32 | """Send ConsumerControl code reports, used by multimedia keyboards, remote controls, etc.""" 33 | 34 | def __init__(self, devices: Sequence[usb_hid.Device]) -> None: 35 | """Create a ConsumerControl object that will send Consumer Control Device HID reports. 36 | 37 | Devices can be a sequence of devices that includes a Consumer Control device or a CC device 38 | itself. A device is any object that implements ``send_report()``, ``usage_page`` and 39 | ``usage``. 40 | """ 41 | self._consumer_device = find_device(devices, usage_page=0x0C, usage=0x01) 42 | 43 | # Reuse this bytearray to send consumer reports. 44 | self._report = bytearray(2) 45 | 46 | # Do a no-op to test if HID device is ready. 47 | # If not, wait a bit and try once more. 48 | try: 49 | self.send(0x0) 50 | except OSError: 51 | time.sleep(1) 52 | self.send(0x0) 53 | 54 | def send(self, consumer_code: int) -> None: 55 | """Send a report to do the specified consumer control action, 56 | and then stop the action (so it will not repeat). 57 | 58 | :param consumer_code: a 16-bit consumer control code. 59 | 60 | Examples:: 61 | 62 | from adafruit_hid.consumer_control_code import ConsumerControlCode 63 | 64 | # Raise volume. 65 | consumer_control.send(ConsumerControlCode.VOLUME_INCREMENT) 66 | 67 | # Advance to next track (song). 68 | consumer_control.send(ConsumerControlCode.SCAN_NEXT_TRACK) 69 | """ 70 | self.press(consumer_code) 71 | self.release() 72 | 73 | def press(self, consumer_code: int) -> None: 74 | """Send a report to indicate that the given key has been pressed. 75 | Only one consumer control action can be pressed at a time, so any one 76 | that was previously pressed will be released. 77 | 78 | :param consumer_code: a 16-bit consumer control code. 79 | 80 | Examples:: 81 | 82 | from adafruit_hid.consumer_control_code import ConsumerControlCode 83 | 84 | # Raise volume for 0.5 seconds 85 | consumer_control.press(ConsumerControlCode.VOLUME_INCREMENT) 86 | time.sleep(0.5) 87 | consumer_control.release() 88 | """ 89 | struct.pack_into(" None: 93 | """Send a report indicating that the consumer control key has been 94 | released. Only one consumer control key can be pressed at a time. 95 | 96 | Examples:: 97 | 98 | from adafruit_hid.consumer_control_code import ConsumerControlCode 99 | 100 | # Raise volume for 0.5 seconds 101 | consumer_control.press(ConsumerControlCode.VOLUME_INCREMENT) 102 | time.sleep(0.5) 103 | consumer_control.release() 104 | """ 105 | self._report[0] = self._report[1] = 0x0 106 | self._consumer_device.send_report(self._report) 107 | -------------------------------------------------------------------------------- /src/lib/adafruit_ntp.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Scott Shawcroft for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_ntp` 7 | ================================================================================ 8 | 9 | Network Time Protocol (NTP) helper for CircuitPython 10 | 11 | * Author(s): Scott Shawcroft 12 | 13 | Implementation Notes 14 | -------------------- 15 | **Hardware:** 16 | **Software and Dependencies:** 17 | 18 | * Adafruit CircuitPython firmware for the supported boards: 19 | https://github.com/adafruit/circuitpython/releases 20 | 21 | """ 22 | import struct 23 | import time 24 | 25 | __version__ = "3.0.10" 26 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_NTP.git" 27 | 28 | NTP_TO_UNIX_EPOCH = 2208988800 # 1970-01-01 00:00:00 29 | 30 | 31 | class NTP: 32 | """Network Time Protocol (NTP) helper module for CircuitPython. 33 | This module does not handle daylight savings or local time. It simply requests 34 | UTC from a NTP server. 35 | """ 36 | 37 | def __init__( 38 | self, 39 | socketpool, 40 | *, 41 | server: str = "0.adafruit.pool.ntp.org", 42 | port: int = 123, 43 | tz_offset: float = 0, 44 | socket_timeout: int = 10, 45 | ) -> None: 46 | """ 47 | :param object socketpool: A socket provider such as CPython's `socket` module. 48 | :param str server: The domain of the ntp server to query. 49 | :param int port: The port of the ntp server to query. 50 | :param float tz_offset: Timezone offset in hours from UTC. Only useful for timezone ignorant 51 | CircuitPython. CPython will determine timezone automatically and adjust (so don't use 52 | this.) For example, Pacific daylight savings time is -7. 53 | :param int socket_timeout: UDP socket timeout, in seconds. 54 | """ 55 | self._pool = socketpool 56 | self._server = server 57 | self._port = port 58 | self._packet = bytearray(48) 59 | self._tz_offset = int(tz_offset * 60 * 60) 60 | self._socket_timeout = socket_timeout 61 | 62 | # This is our estimated start time for the monotonic clock. We adjust it based on the ntp 63 | # responses. 64 | self._monotonic_start = 0 65 | 66 | self.next_sync = 0 67 | 68 | @property 69 | def datetime(self) -> time.struct_time: 70 | """Current time from NTP server. Accessing this property causes the NTP time request, 71 | unless there has already been a recent request. Raises OSError exception if no response 72 | is received within socket_timeout seconds""" 73 | if time.monotonic_ns() > self.next_sync: 74 | self._packet[0] = 0b00100011 # Not leap second, NTP version 4, Client mode 75 | for i in range(1, len(self._packet)): 76 | self._packet[i] = 0 77 | with self._pool.socket(self._pool.AF_INET, self._pool.SOCK_DGRAM) as sock: 78 | sock.settimeout(self._socket_timeout) 79 | sock.sendto(self._packet, (self._server, self._port)) 80 | sock.recvfrom_into(self._packet) 81 | # Get the time in the context to minimize the difference between it and receiving 82 | # the packet. 83 | destination = time.monotonic_ns() 84 | poll = struct.unpack_from("!B", self._packet, offset=2)[0] 85 | self.next_sync = destination + (2**poll) * 1_000_000_000 86 | seconds = struct.unpack_from( 87 | "!I", self._packet, offset=len(self._packet) - 8 88 | )[0] 89 | self._monotonic_start = ( 90 | seconds 91 | + self._tz_offset 92 | - NTP_TO_UNIX_EPOCH 93 | - (destination // 1_000_000_000) 94 | ) 95 | 96 | return time.localtime( 97 | time.monotonic_ns() // 1_000_000_000 + self._monotonic_start 98 | ) 99 | -------------------------------------------------------------------------------- /video-server-python/recognize/opencv/battle_shiny.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import time 3 | import asyncio 4 | import numpy as np 5 | from recognize import frame 6 | from PIL import ImageDraw, ImageFont,Image 7 | import controller.macro as macro 8 | 9 | from recognize.opencv.opencv import OpenCV 10 | 11 | class BattleShiny(OpenCV): 12 | def __init__(self,frame:frame.Frame,opencv_processed_video_frame,controller_action_queue,log_udp_port): 13 | super().__init__(frame,opencv_processed_video_frame,controller_action_queue,log_udp_port) 14 | self._template = None 15 | self._fontText = ImageFont.truetype('resources/font/simsun.ttc', 32, encoding="utf-8") 16 | self._template = cv2.imread("resources/img/battle_shiny.jpg") 17 | self._template = cv2.cvtColor(self._template, cv2.COLOR_BGR2GRAY) 18 | self._template_p = (865,430) 19 | 20 | async def run(self): 21 | last_span_frame_count = 0 22 | span_second = 0 23 | _start_monotonic = time.monotonic() 24 | _frame_count = 0 25 | self._enable_send_action = True 26 | await self.send_action(macro.macro_close_game,1) 27 | await asyncio.sleep(1) 28 | await self.send_action(macro.macro_press_button_a_loop,1) 29 | macro_run = True 30 | while True: 31 | if time.monotonic() - _start_monotonic > 60 and self._enable_send_action: 32 | await self.send_action(macro.macro_close_game,1) 33 | await asyncio.sleep(1) 34 | await self.send_action(macro.macro_press_button_a_loop,1) 35 | _start_monotonic = time.monotonic() 36 | macro_run = True 37 | _frame_count = 0 38 | 39 | data = await self._frame.get_frame() 40 | image = ( 41 | np 42 | .frombuffer(data, np.uint8) 43 | .reshape([self._frame.height, self._frame.width, 3]) 44 | ) 45 | gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 46 | match = cv2.matchTemplate(gray, self._template, cv2.TM_CCOEFF_NORMED) 47 | _,max_val,_,p=cv2.minMaxLoc(match) 48 | if max_val > 0.75 and abs(p[0] - self._template_p[0])<=10 and abs(p[1] - self._template_p[1])<=10: 49 | if macro_run: 50 | await self.send_action(macro.macro_action_clear,1) 51 | macro_run = False 52 | if time.monotonic() - _start_monotonic > 0.15 and _frame_count>0: 53 | last_span_frame_count = _frame_count 54 | span_second = time.monotonic() - _start_monotonic 55 | _frame_count=0 56 | _start_monotonic = time.monotonic() 57 | x = p[0] + self._template.shape[0] - 1 58 | y = p[1] + self._template.shape[1] - 1 59 | if y >= self._frame.height: 60 | x = self._frame.height - 1 61 | if y >= self._frame.width: 62 | x = self._frame.width - 1 63 | image = cv2.rectangle(image, p, (x,y), (0, 255, 0), 4, 4) 64 | else: 65 | _frame_count += 1 66 | 67 | if span_second < 3 and span_second > 0.15 and last_span_frame_count > 0: 68 | img = Image.fromarray(image) 69 | draw = ImageDraw.Draw(img) 70 | draw.text((40, 50), "间隔时间:{:.3f}秒".format(span_second), (0, 255, 0), font=self._fontText) 71 | draw.text((40, 82), "{:d}帧".format(last_span_frame_count), (0, 255, 0), font=self._fontText) 72 | image = np.asarray(img) 73 | try: 74 | self._opencv_processed_video_frame.put(image.tobytes(),False,0) 75 | except: 76 | pass 77 | 78 | if span_second < 2 and span_second > 0.15 and last_span_frame_count > 0 and not macro_run: 79 | if span_second < 0.7: 80 | await self.send_action(macro.macro_close_game,1) 81 | await asyncio.sleep(1) 82 | await self.send_action(macro.macro_press_button_a_loop,1) 83 | macro_run = True 84 | else: 85 | await self.send_action(macro.macro_action_clear,1) 86 | macro_run = False 87 | self._enable_send_action = False -------------------------------------------------------------------------------- /src/tcp_server.py: -------------------------------------------------------------------------------- 1 | import io 2 | import macros 3 | import time 4 | import asyncio 5 | from errno import EAGAIN,ENOTCONN 6 | import wifi 7 | import socketpool 8 | import customize.wifi_connect as wifi_connect 9 | import customize.task_manager as task_manager 10 | 11 | MAXBUF = 1024 12 | 13 | class TcpServer(object): 14 | def __new__(cls, *args, **kwargs): 15 | if not hasattr(cls, '_instance'): 16 | cls._instance = super(TcpServer, cls).__new__(cls) 17 | return cls._instance 18 | 19 | _first = True 20 | def __init__(self): 21 | if TcpServer._first: 22 | TcpServer._first = False 23 | self._on_message = None 24 | self._socket: socketpool.Socket = None 25 | self._clients = [] 26 | 27 | async def start_serve(self,port): 28 | tm = task_manager.TaskManager() 29 | HOST = None 30 | HOST = wifi_connect.ip_address() 31 | if HOST == None: 32 | await asyncio.sleep(5) 33 | if self._socket != None: 34 | self._socket.close() 35 | self._socket: socketpool.Socket = None 36 | self._clients = [] 37 | try: 38 | wifi_connect.reconnect() 39 | except: 40 | pass 41 | tm.create_task(self.start_serve(port)) 42 | return 43 | print("Starting TCP Server socket") 44 | pool = socketpool.SocketPool(wifi.radio) 45 | self._socket = pool.socket(pool.AF_INET, pool.SOCK_STREAM) 46 | self._socket.setblocking(False) 47 | self._socket.bind((str(HOST), port)) 48 | self._socket.listen(1000) 49 | 50 | while True: 51 | await asyncio.sleep_ms(10) 52 | if wifi_connect.ip_address() == None: 53 | tm.create_task(self.start_serve(port)) 54 | self._socket.close() 55 | return 56 | try: 57 | client = None 58 | try: 59 | s = self._socket.accept() 60 | client = s[0] 61 | except OSError as e: 62 | if e.errno != EAGAIN: 63 | raise e 64 | if client != None: 65 | self._clients.append(client) 66 | client.setblocking(False) 67 | tm.create_task(self.tcp_handler(client)) 68 | except OSError as e: 69 | if client != None: 70 | self._clients.remove(client) 71 | 72 | async def tcp_handler(self, client_socket): 73 | buf = bytearray(MAXBUF) 74 | last_active_ts = time.monotonic() 75 | try: 76 | size = 0 77 | while True: 78 | await asyncio.sleep_ms(1) 79 | if time.monotonic() - last_active_ts > 10: 80 | client_socket.send("ping".encode('utf-8')) 81 | last_active_ts = time.monotonic() 82 | bytes_io = io.BytesIO() 83 | while True: 84 | try: 85 | size = client_socket.recv_into(buf,MAXBUF) 86 | bytes_io.write(buf[:size]) 87 | except OSError as e: 88 | if e.errno == EAGAIN: 89 | break 90 | elif e.errno == ENOTCONN: 91 | data = bytes_io.getvalue() 92 | bytes_io.close() 93 | if len(data) > 0 : 94 | last_active_ts = time.monotonic() 95 | data = data.decode('utf-8') 96 | ret = macros.add_joystick_task(data) 97 | self._clients.remove(client_socket) 98 | return 99 | raise e 100 | data = bytes_io.getvalue() 101 | bytes_io.close() 102 | if len(data) > 0 : 103 | last_active_ts = time.monotonic() 104 | data = data.decode('utf-8') 105 | ret = macros.add_joystick_task(data) 106 | if ret and ret != "": 107 | client_socket.send(ret.encode('utf-8')) 108 | # client_socket.send(data.encode('utf-8')) 109 | except: 110 | self._clients.remove(client_socket) -------------------------------------------------------------------------------- /src/hid/joystick/input/pro_controller.py: -------------------------------------------------------------------------------- 1 | from hid.joystick.input.joystick_input import JoyStickInput 2 | 3 | _Input0_Y = 0b1 4 | _Input0_X = 0b10 5 | _Input0_B = 0b100 6 | _Input0_A = 0b1000 7 | _Input0_JCL_SR = 0b10000 8 | _Input0_JCL_SL = 0b100000 9 | _Input0_R = 0b1000000 10 | _Input0_ZR = 0b10000000 11 | 12 | _Input1_Minus = 0b1 13 | _Input1_Plus = 0b10 14 | _Input1_RPress = 0b100 15 | _Input1_LPress = 0b1000 16 | _Input1_Home = 0b10000 17 | _Input1_Capture = 0b100000 18 | 19 | _Input2_Bottom = 0b1 20 | _Input2_Top = 0b10 21 | _Input2_Right = 0b100 22 | _Input2_Left = 0b1000 23 | _Input2_JCR_SR = 0b10000 24 | _Input2_JCR_SL = 0b100000 25 | _Input2_L = 0b1000000 26 | _Input2_ZL = 0b10000000 27 | 28 | class JoyStickInput_PRO_CONTROLLER(JoyStickInput): 29 | 30 | def __init__(self, input_line): 31 | self._buffer = bytearray(11) 32 | self._buffer[0] =0x81 33 | self._buffer[10] =0x00 34 | lx = 0x800 35 | ly = 0x800 36 | rx = 0x800 37 | ry = 0x800 38 | 39 | splits = input_line.upper().split("|", -1) 40 | for s in splits: 41 | s = s.strip() 42 | if s == "Y": 43 | self._buffer[1] |= _Input0_Y 44 | elif s == "X": 45 | self._buffer[1] |= _Input0_X 46 | elif s == "B": 47 | self._buffer[1] |= _Input0_B 48 | elif s == "A": 49 | self._buffer[1] |= _Input0_A 50 | elif s == "JCL_SR": 51 | self._buffer[1] |= _Input0_JCL_SR 52 | elif s == "JCL_SL": 53 | self._buffer[1] |= _Input0_JCL_SL 54 | elif s == "R": 55 | self._buffer[1] |= _Input0_R 56 | elif s == "ZR": 57 | self._buffer[1] |= _Input0_ZR 58 | 59 | elif s == "MINUS": 60 | self._buffer[2] |= _Input1_Minus 61 | elif s == "PLUS": 62 | self._buffer[2] |= _Input1_Plus 63 | elif s == "LPRESS": 64 | self._buffer[2] |= _Input1_LPress 65 | elif s == "RPRESS": 66 | self._buffer[2] |= _Input1_RPress 67 | elif s == "HOME": 68 | self._buffer[2] |= _Input1_Home 69 | elif s == "CAPTURE": 70 | self._buffer[2] |= _Input1_Capture 71 | 72 | elif s == "BOTTOM": 73 | self._buffer[3] |= _Input2_Bottom 74 | elif s == "TOP": 75 | self._buffer[3] |= _Input2_Top 76 | elif s == "RIGHT": 77 | self._buffer[3] |= _Input2_Right 78 | elif s == "LEFT": 79 | self._buffer[3] |= _Input2_Left 80 | elif s == "JCR_SR": 81 | self._buffer[3] |= _Input2_JCR_SR 82 | elif s == "JCR_SL": 83 | self._buffer[3] |= _Input2_JCR_SL 84 | elif s == "L": 85 | self._buffer[3] |= _Input2_L 86 | elif s == "ZL": 87 | self._buffer[3] |= _Input2_ZL 88 | else: 89 | stick = s.split("@", -1) 90 | if len(stick) == 2: 91 | x = 0 92 | y = 0 93 | coordinate = stick[1].split(",", -1) 94 | if len(coordinate) == 2: 95 | x = self._coordinate_str_convert_int(coordinate[0]) 96 | y = self._coordinate_str_convert_int(coordinate[1]) 97 | x = (x + 128) * 16 98 | y = (y * (-1) + 128) * 16 99 | if x > 0xfff: 100 | x = 0xfff 101 | if y > 0xfff: 102 | y = 0xfff 103 | if stick[0] == "LSTICK": 104 | lx = x 105 | ly = y 106 | elif stick[0] == "RSTICK": 107 | rx = x 108 | ry = y 109 | self._buffer[4] = lx & 0xff 110 | self._buffer[5] = ((lx >> 8) & 0x0f) | ((ly & 0x0f) << 4) 111 | self._buffer[6] = (ly >> 4) & 0xff 112 | self._buffer[7] = rx & 0xff 113 | self._buffer[8] = ((rx >> 8) & 0x0f) | ((ry & 0x0f) << 4) 114 | self._buffer[9] = (ry >> 4) & 0xff 115 | 116 | def _coordinate_str_convert_int(self, str): 117 | v = 0 118 | try: 119 | v = int(float(str)) 120 | except: 121 | pass 122 | if v < -128: 123 | v = -128 124 | elif v > 127: 125 | v = 127 126 | return v 127 | 128 | def buffer(self): 129 | return self._buffer -------------------------------------------------------------------------------- /src/macros/action.py: -------------------------------------------------------------------------------- 1 | from . import macro,paras 2 | 3 | 4 | class Action(object): 5 | def __init__(self, macro_name: str,in_paras:dict = dict()): 6 | self._macro = macro.Macro() 7 | n = self._macro.get_node(macro_name) 8 | self._head = n[0] 9 | self._paras = paras.Paras(n[1],in_paras) 10 | self._current = self._head 11 | self._current_node_link_cycle_times = 1 12 | self._waiting_node = [] 13 | self._body = self._head 14 | self._body_current_node_link_cycle_times = 1 15 | self._body_waiting_node = [] 16 | 17 | def _jump_node(self): 18 | action_line = self._current.action 19 | if not action_line.startswith("["): 20 | return False 21 | splits = action_line.split("]") 22 | key = splits[0][1:] 23 | times = 0 24 | if splits[1].startswith("?"): 25 | if self._paras.get_bool(splits[1][1:]): 26 | times = 1 27 | elif splits[1].startswith("*"): 28 | times = self._paras.get_int(splits[1][1:]) 29 | else: 30 | times = 1 31 | n = self._macro.get_node(key) 32 | node = n[0] 33 | if node.action != macro._FINISHED_LINE and times >= 1: 34 | self._waiting_node.append( 35 | [self._current.next, self._current_node_link_cycle_times]) 36 | self._current = node 37 | self._current_node_link_cycle_times = times 38 | return True 39 | elif self._current != macro._FINISHED_LINE and times < 1: 40 | self._current = self._current.next 41 | return True 42 | return False 43 | 44 | def _return_jump(self): 45 | if self._current.action != macro._FINISHED_LINE: 46 | return 47 | if self._current_node_link_cycle_times > 1: 48 | self._current = self._current.head 49 | self._current_node_link_cycle_times -= 1 50 | return 51 | elif len(self._waiting_node) > 0: 52 | ret = self._waiting_node.pop() 53 | self._current = ret[0] 54 | self._current_node_link_cycle_times = ret[1] 55 | 56 | if self._current.action == macro._FINISHED_LINE: 57 | self._return_jump() 58 | 59 | def pop(self): 60 | line: str = None 61 | is_finish = False 62 | while True: 63 | if self._current == None: 64 | return None, True 65 | while self._jump_node(): 66 | pass 67 | line = self._current.action 68 | if line == "body:": 69 | self._current = self._current.next 70 | self._return_jump() 71 | if self._current.action == macro._FINISHED_LINE: 72 | return None, True 73 | line = self._current.action 74 | self._body = self._current 75 | self._body_current_node_link_cycle_times = self._current_node_link_cycle_times 76 | self._body_waiting_node = [] 77 | for row in self._waiting_node: 78 | self._body_waiting_node.append(row) 79 | continue 80 | elif line.startswith("EXEC>"): 81 | self._paras.exec_str(line[5:]) 82 | self._current = self._current.next 83 | self._return_jump() 84 | if self._current.action == macro._FINISHED_LINE: 85 | return None, True 86 | line = self._current.action 87 | continue 88 | elif self._current.action == macro._FINISHED_LINE: 89 | self._return_jump() 90 | if self._current.action == macro._FINISHED_LINE: 91 | return None, True 92 | else: 93 | break 94 | self._current = self._current.next 95 | self._return_jump() 96 | if line: 97 | var_strs = self.extract_action_variable_str(line) 98 | for var_str in var_strs: 99 | line = line.replace("-*" + var_str + "*-", str(self._paras.get_float(var_str))) 100 | return line, is_finish 101 | 102 | def extract_action_variable_str(self, text): 103 | parts = text.split('-*') 104 | result = [part.split('*-')[0] for part in parts if '*-' in part] 105 | return result 106 | 107 | def reset(self): 108 | self._current = self._head 109 | self._current_node_link_cycle_times = 1 110 | self._waiting_node = [] 111 | 112 | def cycle_reset(self): 113 | self._current = self._body 114 | self._current_node_link_cycle_times = self._body_current_node_link_cycle_times 115 | self._waiting_node = [] 116 | for row in self._body_waiting_node: 117 | self._waiting_node.append(row) 118 | -------------------------------------------------------------------------------- /src/resources/macros/pokemon/scarletviolet/eggs.m: -------------------------------------------------------------------------------- 1 | --朱紫孵蛋-- 2 | 3 | body: 4 | EXEC>last_page=int(last_page);last_col=int(last_col);start_col=int(start_col);flame_body=flame_body.lower()=='true' 5 | EXEC>current_page=0;current_col=start_col; 6 | EXEC>loop_times=last_page*6+last_col-start_col+1 7 | EXEC>cycles=int(cycles)*2 8 | { 9 | EXEC>cycles=int(cycles)*2 10 | }?not|space|flame_body 11 | 12 | # 打开盒子 13 | X:0.05 14 | 1 15 | A:0.01->0.008->A:0.05 16 | 3 17 | { 18 | # 判断是否需要翻页 19 | { 20 | R:0.05 21 | 0.5 22 | EXEC>current_col=0 23 | EXEC>current_page=current_page+1 24 | }?current_col>5 25 | # 选择列 26 | { 27 | RIGHT:0.01->0.008->RIGHT:0.05 28 | 0.2 29 | }*current_col 30 | 0.4 31 | MINUS:0.01->0.008->MINUS:0.05 32 | 0.8 33 | { 34 | BOTTOM:0.01->0.008->BOTTOM:0.05 35 | 0.2 36 | }*4 37 | 0.6 38 | A:0.01->0.008->A:0.05 39 | 0.8 40 | { 41 | LEFT:0.01->0.008->LEFT:0.05 42 | 0.2 43 | }*(current_col+1) 44 | BOTTOM:0.01->0.008->BOTTOM:0.05 45 | 0.8 46 | A:0.01->0.008->A:0.05 47 | 0.8 48 | # 关闭盒子 49 | { 50 | B:0.05 51 | 0.05 52 | }*15 53 | 0.8 54 | 55 | L:0.05 56 | 0.6 57 | { 58 | LStick@-0,-127|RStick@-127,0:0.5->LStick@-0,-127|RStick@-127,0|LPRESS:0.1->LStick@-0,-127|RStick@-127,0:5.4->~ 59 | }*int((cycles+4)/5) 60 | 1 61 | { 62 | A:0.1 63 | 0.1 64 | }*90 65 | 4 66 | { 67 | L:0.05 68 | 0.6 69 | LPRESS:0.01 70 | LPRESS:0.01 71 | { 72 | LStick@-0,-127|RStick@-127,0:0.5->LStick@-0,-127|RStick@-127,0|LPRESS:0.1->LStick@-0,-127|RStick@-127,0:12->~ 73 | }flame_body 74 | { 75 | LStick@-0,-127|RStick@-127,0:0.5->LStick@-0,-127|RStick@-127,0|LPRESS:0.1->LStick@-0,-127|RStick@-127,0:20->~ 76 | }?not|space|flame_body 77 | 1 78 | { 79 | A:0.1 80 | 0.1 81 | }*90 82 | 4 83 | }*4 84 | 85 | # 打开盒子 86 | X:0.05 87 | 0.8 88 | A:0.05 89 | 3 90 | LEFT:0.01->0.008->LEFT:0.05 91 | 0.2 92 | BOTTOM:0.01->0.008->BOTTOM:0.05 93 | 0.6 94 | MINUS:0.01->0.008->MINUS:0.05 95 | 0.8 96 | { 97 | BOTTOM:0.01->0.008->BOTTOM:0.05 98 | 0.2 99 | }*4 100 | 0.6 101 | A:0.01->0.008->A:0.05 102 | 0.8 103 | TOP:0.01->0.008->TOP:0.05 104 | 0.2 105 | { 106 | RIGHT:0.01->0.008->RIGHT:0.05 107 | 0.2 108 | }*current_col+1 109 | 0.6 110 | A:0.01->0.008->A:0.05 111 | 0.8 112 | { 113 | LEFT:0.01->0.008->LEFT:0.05 114 | 0.2 115 | }*current_col 116 | EXEC>current_col=current_col+1 117 | }*loop_times 118 | 119 | # 关闭盒子 120 | { 121 | B:0.05 122 | 0.05 123 | }*15 124 | 0.7 125 | 126 | 127 | 128 | 129 | --朱紫野餐取蛋-- 130 | 131 | [pokemon.scarletviolet.common.restart_game] 132 | X:0.1 133 | 0.8 134 | LStick@127,0:0.05 135 | 0.1 136 | LStick@0,127:0.05 137 | 0.1 138 | LStick@0,127:0.035 139 | 0.1 140 | body: 141 | A:0.1 142 | 0.1 143 | 9 144 | # 往前走一步,开始做料理 145 | LStick@0,-127:0.2 146 | 0.8 147 | A:0.1 148 | 0.5 149 | A:0.1 150 | 5 151 | # 选择超级花生酱三明治(配方:香蕉,花生酱,黄油 各1) 152 | { 153 | LStick@0,127:0.05 154 | 0.3 155 | }*8 156 | A:0.1 157 | 0.7 158 | A:0.1 159 | 7 160 | { 161 | LStick@0,-127:0.5 162 | 0.1 163 | A:0.1->LStick@0,127|A:0.48->A:0.5 164 | 0.1 165 | }*3 166 | 0.6 167 | A:0.1 168 | 2 169 | A:0.1 170 | 3.5 171 | A:0.1 172 | 12 173 | A:0.1 174 | 9 175 | A:0.1 176 | 0.5 177 | 15 178 | A:0.1 179 | 2 180 | LStick@127,0:0.4->LStick@0,-127:0.4->LStick@-127,0:0.16 181 | { 182 | # 单次循环周期120s 183 | 106 184 | { 185 | A:0.025 186 | 0.1 187 | }*100 188 | { 189 | B:0.05 190 | 0.2 191 | }*4 192 | 0.5 193 | }*int(get_eggs_times) 194 | 1 195 | { 196 | Y:0.05 197 | 0.5 198 | }*3 199 | A:0.05 200 | 0.5 201 | A:0.05 202 | 2.5 203 | { 204 | A:0.05 205 | 0.1 206 | }*10 207 | L:0.05 208 | 1 209 | X:0.1 210 | 0.8 211 | 212 | 213 | 214 | --朱紫放生-- 215 | 216 | body: 217 | EXEC>current_page=0;current_col=int(start_col); 218 | EXEC>loop_times=int(last_page)*6+int(last_col)-int(start_col)+1 219 | 220 | # 打开盒子 221 | X:0.05 222 | 0.8 223 | A:0.05 224 | 3 225 | # 选择列 226 | { 227 | LStick@127,0:0.05 228 | 0.2 229 | }*current_col 230 | { 231 | # 判断是否需要翻页 232 | { 233 | R:0.05 234 | 0.7 235 | LStick@127,0:0.05 236 | 0.2 237 | EXEC>current_col=0 238 | EXEC>current_page=current_page+1 239 | }?current_col>5 240 | 0.4 241 | [release_a_pokemon] 242 | { 243 | LStick@0,127:0.05 244 | 0.4 245 | [release_a_pokemon] 246 | }*4 247 | { 248 | LStick@0,127:0.05 249 | 0.2 250 | }*3 251 | LStick@127,0:0.05 252 | 0.4 253 | EXEC>current_col=current_col+1 254 | }*loop_times 255 | 256 | # 关闭盒子 257 | { 258 | B:0.05 259 | 0.05 260 | }*15 261 | 0.7 262 | 263 | 264 | 265 | --放生1只精灵-- 266 | 267 | A:0.01->0.008->A:0.05 268 | 0.6 269 | TOP:0.01->0.008->TOP:0.05 270 | 0.1 271 | TOP:0.01->0.008->TOP:0.05 272 | 0.1 273 | A:0.01->0.008->A:0.05 274 | 0.6 275 | TOP:0.01->0.008->TOP:0.05 276 | 0.1 277 | A:0.01->0.008->A:0.05 278 | 1.6 279 | A:0.01->0.008->A:0.05 280 | 0.6 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp32-circuitpython-switch-joystick 2 | 3 | A virtual HID USB joystick created using ESP32S2/S3/RP2040 4 | 5 | ## 硬件需求 6 | 7 | 1. Switch主机 * 1 8 | 2. ESP32S2/ESP32S3/RP2040开发板 * 1 9 | - 本人测试用设备: 10 | - LOLIN S2 Mini:ESP32S2芯片,单核240MHz,N4R2 12.5元 注意近期有10%左右的网友无法刷入circuitpython官方固件,猜测可能是模块问题,如果担心设备问题,尽量选择Raspberry Pi Pico 11 | - ESP32-S3-DevKitC-1-N16R8:ESP32S3芯片,双核240MHz,N16R8,支持蓝牙 59元 12 | - Raspberry Pi Pico (W): 带W版本 包含网络模块 51元,不带W 25元 13 | - 由于我希望降低大家的使用成本,后续的研发,测试,我将在LOLIN S2 Mini上进行 14 | 3. USB线(连接主机与开发板) * 1 15 | 16 | 17 | 18 | ## 常见问题汇总 19 | 20 | [跳转](./SCRIPTS.md) 21 | 22 | 使用前请一定要看一下 23 | 24 | ## 功能 25 | 26 | 1. 模拟NS手柄,控制NS主机 27 | 2. 支持自定义宏脚本,实现游戏自动化 28 | 3. 内部集成部分宝可梦剑盾功能的宏脚本 29 | 4. TCP管理基本功能 30 | 31 | ## 预计追加功能 32 | 33 | ### 近期 34 | 35 | 1. ~~LCD单色液晶显示支持(已废弃,计划采用WEB方式代替)~~ 36 | 37 | 2. ~~实体按键支持(已废弃,计划采用WEB方式代替)~~ 38 | 39 | 3. Web管理(脚本的启动、停止、参数配置) 40 | 41 | 4. 远程管理功能(首选HTTP服务,其次TCP/UDP或MQTT,尽量不依赖其他服务器或客户端软件,独自实现远程管理功能) 42 | 43 | ### 计划 44 | 45 | 1. 通过NS的HDMI输出连接PC端,实现简单图像识别,增强脚本功能 46 | 47 | 2. **宝可梦朱紫脚本制作** 48 | 49 | ## 简介 50 | 51 | ### ESP32S3固件 52 | 53 | #### 固件编译 54 | 55 | ##### 8.0.0-beta.4 ~ 56 | 57 | 直接使用circuitpython官方固件即可,[固件下载链接](https://circuitpython.org/downloads)()。 58 | 59 | ##### 7.3.3 60 | 61 | 1. 设备OTG口连接PC,Windows与Mac都可 62 | 63 | 2. 如果设备为 **EPS32S3-N16R8** 或 **LOLIN S2 Mini**,可直接使用**项目中打包好固件**,不需要自行编译,直接进行固件写入操作 64 | 65 | 3. PC安装docker 66 | 67 | 4. 修改firmware/esp32s3-n16r8/Dockerfile文件: 68 | 69 | ```dockerfile 70 | WORKDIR /root/circuitpython/ports/espressif/boards 71 | # https://github.com/adafruit/circuitpython/tree/main/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8r2 72 | RUN cp -r espressif_esp32s3_devkitc_1_n8r2 espressif_esp32s3_devkitc_1_n16r8 && \ 73 | sed -i 's/ESP32-S3-DevKitC-1-N8R2/ESP32-S3-DevKitC-1-N16R8/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.h && \ 74 | sed -i 's/USB_VID = 0x303A/USB_VID = 0x0F0D/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 75 | sed -i 's/USB_PID = 0x7003/USB_PID = 0x00C1/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 76 | sed -i 's/USB_PRODUCT = "ESP32-S3-DevKitC-1-N8R2"/USB_PRODUCT = "HORI CO.,LTD."/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 77 | sed -i 's/USB_MANUFACTURER = "Espressif"/USB_MANUFACTURER = "HORIPAD S"/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 78 | # CIRCUITPY_ESP_FLASH_MODE DIO -> QIO 79 | sed -i 's/dio/qio/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 80 | # CIRCUITPY_ESP_FLASH_SIZE 8MB -> 16MB 81 | sed -i 's/8MB/16MB/g' espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 82 | echo 'CIRCUITPY_ESP32_CAMERA = 0' >> espressif_esp32s3_devkitc_1_n16r8/mpconfigboard.mk && \ 83 | # QUAD -> OCTAL 84 | sed -i 's/CONFIG_SPIRAM_MODE_QUAD/CONFIG_SPIRAM_MODE_OCT/g' espressif_esp32s3_devkitc_1_n16r8/sdkconfig && \ 85 | # 2MB -> 8MB 86 | sed -i 's/2097152/8388608/g' espressif_esp32s3_devkitc_1_n16r8/sdkconfig 87 | ``` 88 | 89 | 由于官方固件不支持N16R8,所以在N8R2上进行修改。请修改时,参考自己的设备,根据自己设备信息修改。 90 | 91 | *注:VID与PID改为0x0F0D与0x00C1是依据模拟设备修改,不修改是否可以使用我没做尝试,还是尽量修改。* 92 | 93 | 5. 进入firmware/esp32s3-n16r8目录,运行下面脚本进行编译: 94 | 95 | ```shell 96 | docker build . -t circuitpython 97 | docker run -d --rm -v $(PWD)/build:/root/build circuitpython 98 | ``` 99 | 100 | 由于网络环境等因素问题,可能有些资源下载比较慢或经常中断,这个问题需要自行解决。 101 | 102 | 等待很长一段时间后,编译完成,build目录会生成大量文件,保留firmware.bin或firmware.uf2文件即可(依据自己刷入固件的方式) 103 | 104 | #### 固件写入 105 | 106 | 1. Chrome浏览器打开[ESP Web Flasher 页面](https://nabucasa.github.io/esp-web-flasher/)()刷写固件 107 | 108 | 2. ESP32进入固件写入模式(按住0或BOOT,点击RST,松开0或BOOT即可) 109 | 110 | 3. 页面操作,连接设备,**Baudrate**选择**460800 (LOLIN S2 Mini)** / **921600 (ESP32S3N16R8)** 111 | 112 | 4. 擦除设备,选择bin固件,同时把起始地址改为0x0,开始写入 113 | 114 | 5. 写入完成后双击设备上的RST按钮,或重新插拔设备即可 115 | 116 | #### 程序部署 117 | 118 | 1. 修改src/resources/config.json中的wifi节点,type分为wifi和ap两种模式。 119 | 120 | 2. 把工作目录src下所有文件目录,拷贝至ESP32S3被识别出盘符即可,相同文件目录覆盖 121 | 122 | 3. 下载依赖库 123 | 124 | - 本地安装python,并且配置环境变量(python运行目录,pip下载包运行目录加入Path)。 125 | 126 | - 运行下面代码: 127 | 128 | 本地安装circup: 129 | 130 | ``` 131 | pip3 install circup 132 | ``` 133 | 134 | esp32安装库: 135 | 136 | ``` 137 | circup install asyncio adafruit-circuitpython-hid adafruit-circuitpython-ntp 138 | ``` 139 | 140 |
恭喜!环境部署成功!
141 | 142 | ## 脚本语法 143 | 144 | [跳转](./SCRIPTS.md) 145 | 146 | ## 配置文件 147 | 148 | 配置文件路径:ESP32目录/resources/config.json 149 | 150 | ## 远程管理 151 | 152 | 采用TCP方式进行管理,默认开启5000端口 153 | 154 | ### 运行效果 155 | 156 | 157 | 158 | 159 | 160 | ​ 后续,删除游戏,并重新安装后,卡顿问题解决,对运行效率有需求的朋友可自行修改脚本。 161 | 162 | ​ 更新OpenCV图像识别方式,效果如下: 163 | 164 | 165 | 166 | #### 开发笔记(不定期更新) 167 | 168 | [跳转](./NOTE.md) 169 | 170 | #### 特别感谢 171 | 172 | - 燃烧の小宇宙 提供朱紫无限复制道具(1.01版BUG)脚本 173 | -------------------------------------------------------------------------------- /src/lib/adafruit_hid/mouse.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid.mouse.Mouse` 7 | ==================================================== 8 | 9 | * Author(s): Dan Halbert 10 | """ 11 | import time 12 | 13 | from . import find_device 14 | 15 | try: 16 | from typing import Sequence 17 | import usb_hid 18 | except ImportError: 19 | pass 20 | 21 | 22 | class Mouse: 23 | """Send USB HID mouse reports.""" 24 | 25 | LEFT_BUTTON = 1 26 | """Left mouse button.""" 27 | RIGHT_BUTTON = 2 28 | """Right mouse button.""" 29 | MIDDLE_BUTTON = 4 30 | """Middle mouse button.""" 31 | 32 | def __init__(self, devices: Sequence[usb_hid.Device]): 33 | """Create a Mouse object that will send USB mouse HID reports. 34 | 35 | Devices can be a sequence of devices that includes a keyboard device or a keyboard device 36 | itself. A device is any object that implements ``send_report()``, ``usage_page`` and 37 | ``usage``. 38 | """ 39 | self._mouse_device = find_device(devices, usage_page=0x1, usage=0x02) 40 | 41 | # Reuse this bytearray to send mouse reports. 42 | # report[0] buttons pressed (LEFT, MIDDLE, RIGHT) 43 | # report[1] x movement 44 | # report[2] y movement 45 | # report[3] wheel movement 46 | self.report = bytearray(4) 47 | 48 | # Do a no-op to test if HID device is ready. 49 | # If not, wait a bit and try once more. 50 | try: 51 | self._send_no_move() 52 | except OSError: 53 | time.sleep(1) 54 | self._send_no_move() 55 | 56 | def press(self, buttons: int) -> None: 57 | """Press the given mouse buttons. 58 | 59 | :param buttons: a bitwise-or'd combination of ``LEFT_BUTTON``, 60 | ``MIDDLE_BUTTON``, and ``RIGHT_BUTTON``. 61 | 62 | Examples:: 63 | 64 | # Press the left button. 65 | m.press(Mouse.LEFT_BUTTON) 66 | 67 | # Press the left and right buttons simultaneously. 68 | m.press(Mouse.LEFT_BUTTON | Mouse.RIGHT_BUTTON) 69 | """ 70 | self.report[0] |= buttons 71 | self._send_no_move() 72 | 73 | def release(self, buttons: int) -> None: 74 | """Release the given mouse buttons. 75 | 76 | :param buttons: a bitwise-or'd combination of ``LEFT_BUTTON``, 77 | ``MIDDLE_BUTTON``, and ``RIGHT_BUTTON``. 78 | """ 79 | self.report[0] &= ~buttons 80 | self._send_no_move() 81 | 82 | def release_all(self) -> None: 83 | """Release all the mouse buttons.""" 84 | self.report[0] = 0 85 | self._send_no_move() 86 | 87 | def click(self, buttons: int) -> None: 88 | """Press and release the given mouse buttons. 89 | 90 | :param buttons: a bitwise-or'd combination of ``LEFT_BUTTON``, 91 | ``MIDDLE_BUTTON``, and ``RIGHT_BUTTON``. 92 | 93 | Examples:: 94 | 95 | # Click the left button. 96 | m.click(Mouse.LEFT_BUTTON) 97 | 98 | # Double-click the left button. 99 | m.click(Mouse.LEFT_BUTTON) 100 | m.click(Mouse.LEFT_BUTTON) 101 | """ 102 | self.press(buttons) 103 | self.release(buttons) 104 | 105 | def move(self, x: int = 0, y: int = 0, wheel: int = 0) -> None: 106 | """Move the mouse and turn the wheel as directed. 107 | 108 | :param x: Move the mouse along the x axis. Negative is to the left, positive 109 | is to the right. 110 | :param y: Move the mouse along the y axis. Negative is upwards on the display, 111 | positive is downwards. 112 | :param wheel: Rotate the wheel this amount. Negative is toward the user, positive 113 | is away from the user. The scrolling effect depends on the host. 114 | 115 | Examples:: 116 | 117 | # Move 100 to the left. Do not move up and down. Do not roll the scroll wheel. 118 | m.move(-100, 0, 0) 119 | # Same, with keyword arguments. 120 | m.move(x=-100) 121 | 122 | # Move diagonally to the upper right. 123 | m.move(50, 20) 124 | # Same. 125 | m.move(x=50, y=-20) 126 | 127 | # Roll the mouse wheel away from the user. 128 | m.move(wheel=1) 129 | """ 130 | # Send multiple reports if necessary to move or scroll requested amounts. 131 | while x != 0 or y != 0 or wheel != 0: 132 | partial_x = self._limit(x) 133 | partial_y = self._limit(y) 134 | partial_wheel = self._limit(wheel) 135 | self.report[1] = partial_x & 0xFF 136 | self.report[2] = partial_y & 0xFF 137 | self.report[3] = partial_wheel & 0xFF 138 | self._mouse_device.send_report(self.report) 139 | x -= partial_x 140 | y -= partial_y 141 | wheel -= partial_wheel 142 | 143 | def _send_no_move(self) -> None: 144 | """Send a button-only report.""" 145 | self.report[1] = 0 146 | self.report[2] = 0 147 | self.report[3] = 0 148 | self._mouse_device.send_report(self.report) 149 | 150 | @staticmethod 151 | def _limit(dist: int) -> int: 152 | return min(127, max(-127, dist)) 153 | -------------------------------------------------------------------------------- /src/hid/joystick/hori.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | from hid.joystick.input.hori import JoyStickInput_HORI_S 4 | from hid.joystick.joystick import JoyStick 5 | import hid.device 6 | 7 | _Mini_Key_Send_Span_ns = 3000000 8 | 9 | class JoyStick_HORI_S(JoyStick): 10 | def __new__(cls, *args, **kwargs): 11 | if not hasattr(cls, '_instance'): 12 | cls._instance = super(JoyStick_HORI_S, cls).__new__(cls) 13 | return cls._instance 14 | 15 | _first = True 16 | 17 | def __init__(self): 18 | if JoyStick_HORI_S._first: 19 | JoyStick_HORI_S._first = False 20 | device = hid.device.get_device(hid.device.Device_HORIPAD_S) 21 | self._joystick_device = device.find_device() 22 | self._last_send_monotonic_ns = 0 23 | self._realtime_data_lock = asyncio.Lock() 24 | self._is_realtime = False 25 | self._realtime_action = "" 26 | self._realtime_task = None 27 | try: 28 | self._sync_release() 29 | except OSError: 30 | time.sleep(1) 31 | self._sync_release() 32 | 33 | def _sync_release(self): 34 | self._sync_send(JoyStickInput_HORI_S("")) 35 | 36 | def _sync_send(self,input:JoyStickInput_HORI_S): 37 | self._joystick_device.send_report(input.buffer()) 38 | self._last_send_monotonic_ns = time.monotonic_ns() 39 | 40 | async def _send(self, input_line: str = "",earliest_send_key_monotonic_ns=0): 41 | earliest = self._last_send_monotonic_ns + _Mini_Key_Send_Span_ns 42 | if earliest < earliest_send_key_monotonic_ns: 43 | earliest = earliest_send_key_monotonic_ns 44 | input = JoyStickInput_HORI_S(input_line) 45 | loop = 0 46 | while True: 47 | loop += 1 48 | now = time.monotonic_ns() 49 | if now >= earliest: 50 | break 51 | elif now < earliest - _Mini_Key_Send_Span_ns: 52 | ms = int((earliest - now - _Mini_Key_Send_Span_ns)/1000000) 53 | await asyncio.sleep_ms(ms) 54 | else: 55 | time.sleep(0.0001) 56 | t1 = time.monotonic_ns() 57 | self._sync_send(input) 58 | t2 = time.monotonic_ns() 59 | # print((t2 - t1)/1000000,(t1 - earliest)/1000000,loop,input_line) 60 | 61 | async def _key_press(self, inputs = []): 62 | last_action = "" 63 | release_monotonic_ns = 0 64 | for input_line in inputs: 65 | last_action = input_line 66 | if input_line == "~": 67 | continue 68 | await self._send(input_line[0],release_monotonic_ns) 69 | release_monotonic_ns = self._last_send_monotonic_ns + input_line[1]*1000000000 70 | if last_action != "~": 71 | await self.release(release_monotonic_ns) 72 | 73 | async def _start_realtime_async(self): 74 | async with self._realtime_data_lock: 75 | self._realtime_action = "" 76 | await self._send(self._realtime_action) 77 | last_action = "" 78 | last_action_monotonic = time.monotonic() 79 | while self._is_realtime: 80 | await asyncio.sleep_ms(int(_Mini_Key_Send_Span_ns/1000000)) 81 | action = None 82 | if time.monotonic()-last_action_monotonic > 2: 83 | last_action = "clear" 84 | async with self._realtime_data_lock: 85 | if self._realtime_action != last_action: 86 | action = self._realtime_action 87 | last_action = action 88 | last_action_monotonic = time.monotonic() 89 | if action != None: 90 | await self._send(action) 91 | await self._send("") 92 | 93 | async def _stop_realtime_async(self): 94 | self._is_realtime = False 95 | if not self._realtime_task: 96 | return 97 | await self._realtime_task 98 | self._realtime_task = None 99 | 100 | async def send_realtime_action(self,action:str): 101 | async with self._realtime_data_lock: 102 | self._realtime_action = action.strip() 103 | 104 | async def release(self,release_monotonic_ns:float = 0): 105 | await self._send("",release_monotonic_ns) 106 | 107 | async def do_action(self, action_line: str = ""): 108 | inputs = [] 109 | actions = action_line.split("->") 110 | for action in actions: 111 | splits = action.split(":") 112 | if len(splits) > 2: 113 | continue 114 | p1 = splits[0] 115 | p2 = 0.1 116 | if len(splits) == 1: 117 | try: 118 | p2 = float(splits[0]) 119 | p1 = "" 120 | except: 121 | pass 122 | else: 123 | try: 124 | p2 = float(splits[1]) 125 | except: 126 | pass 127 | inputs.append((p1,p2)) 128 | await self._key_press(inputs) 129 | 130 | def start_realtime(self): 131 | if self._realtime_task: 132 | return 133 | self._is_realtime = True 134 | self._realtime_task = asyncio.create_task(self._start_realtime_async()) 135 | 136 | def stop_realtime(self): 137 | self._is_realtime = False 138 | self._realtime_task = None 139 | -------------------------------------------------------------------------------- /src/lib/adafruit_httpserver/route.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2022 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | """ 5 | `adafruit_httpserver.route` 6 | ==================================================== 7 | * Author(s): Dan Halbert, Michał Pokusa 8 | """ 9 | 10 | try: 11 | from typing import Callable, List, Set, Union, Tuple, TYPE_CHECKING 12 | 13 | if TYPE_CHECKING: 14 | from .response import Response 15 | except ImportError: 16 | pass 17 | 18 | import re 19 | 20 | from .methods import GET 21 | 22 | 23 | class _Route: 24 | """Route definition for different paths, see `adafruit_httpserver.server.Server.route`.""" 25 | 26 | def __init__( 27 | self, 28 | path: str = "", 29 | methods: Union[str, Set[str]] = GET, 30 | append_slash: bool = False, 31 | ) -> None: 32 | self._validate_path(path) 33 | 34 | self.parameters_names = [ 35 | name[1:-1] for name in re.compile(r"/[^<>]*/?").split(path) if name != "" 36 | ] 37 | self.path = re.sub(r"<\w+>", r"([^/]+)", path).replace("....", r".+").replace( 38 | "...", r"[^/]+" 39 | ) + ("/?" if append_slash else "") 40 | self.methods = methods if isinstance(methods, set) else {methods} 41 | 42 | @staticmethod 43 | def _validate_path(path: str) -> None: 44 | if not path.startswith("/"): 45 | raise ValueError("Path must start with a slash.") 46 | 47 | if "<>" in path: 48 | raise ValueError("All URL parameters must be named.") 49 | 50 | def match(self, other: "_Route") -> Tuple[bool, List[str]]: 51 | """ 52 | Checks if the route matches the other route. 53 | 54 | If the route contains parameters, it will check if the ``other`` route contains values for 55 | them. 56 | 57 | Returns tuple of a boolean and a list of strings. The boolean indicates if the routes match, 58 | and the list contains the values of the url parameters from the ``other`` route. 59 | 60 | Examples:: 61 | 62 | route = _Route("/example", GET, True) 63 | 64 | other1a = _Route("/example", GET) 65 | other1b = _Route("/example/", GET) 66 | route.matches(other1a) # True, [] 67 | route.matches(other1b) # True, [] 68 | 69 | other2 = _Route("/other-example", GET) 70 | route.matches(other2) # False, [] 71 | 72 | ... 73 | 74 | route = _Route("/example/", GET) 75 | 76 | other1 = _Route("/example/123", GET) 77 | route.matches(other1) # True, ["123"] 78 | 79 | other2 = _Route("/other-example", GET) 80 | route.matches(other2) # False, [] 81 | 82 | ... 83 | 84 | route1 = _Route("/example/.../something", GET) 85 | other1 = _Route("/example/123/something", GET) 86 | route1.matches(other1) # True, [] 87 | 88 | route2 = _Route("/example/..../something", GET) 89 | other2 = _Route("/example/123/456/something", GET) 90 | route2.matches(other2) # True, [] 91 | """ 92 | 93 | if not other.methods.issubset(self.methods): 94 | return False, [] 95 | 96 | regex_match = re.match(f"^{self.path}$", other.path) 97 | if regex_match is None: 98 | return False, [] 99 | 100 | return True, regex_match.groups() 101 | 102 | def __repr__(self) -> str: 103 | path = repr(self.path) 104 | methods = repr(self.methods) 105 | 106 | return f"_Route(path={path}, methods={methods})" 107 | 108 | 109 | class _Routes: 110 | """A collection of routes and their corresponding handlers.""" 111 | 112 | def __init__(self) -> None: 113 | self._routes: List[_Route] = [] 114 | self._handlers: List[Callable] = [] 115 | 116 | def add(self, route: _Route, handler: Callable): 117 | """Adds a route and its handler to the collection.""" 118 | 119 | self._routes.append(route) 120 | self._handlers.append(handler) 121 | 122 | def find_handler(self, route: _Route) -> Union[Callable["...", "Response"], None]: 123 | """ 124 | Finds a handler for a given route. 125 | 126 | If route used URL parameters, the handler will be wrapped to pass the parameters to the 127 | handler. 128 | 129 | Example:: 130 | 131 | @server.route("/example/", GET) 132 | def route_func(request, my_parameter): 133 | ... 134 | request.path == "/example/123" # True 135 | my_parameter == "123" # True 136 | """ 137 | found_route, _route = False, None 138 | 139 | for _route in self._routes: 140 | matches, parameters_values = _route.match(route) 141 | 142 | if matches: 143 | found_route = True 144 | break 145 | 146 | if not found_route: 147 | return None 148 | 149 | handler = self._handlers[self._routes.index(_route)] 150 | 151 | keyword_parameters = dict(zip(_route.parameters_names, parameters_values)) 152 | 153 | def wrapped_handler(request): 154 | return handler(request, **keyword_parameters) 155 | 156 | return wrapped_handler 157 | 158 | def __repr__(self) -> str: 159 | return f"_Routes({repr(self._routes)})" 160 | -------------------------------------------------------------------------------- /src/hid/joystick/input/hori.py: -------------------------------------------------------------------------------- 1 | from hid.joystick.input.joystick_input import JoyStickInput 2 | 3 | _Input0_Y = 0b1 4 | _Input0_B = 0b10 5 | _Input0_A = 0b100 6 | _Input0_X = 0b1000 7 | _Input0_L = 0b10000 8 | _Input0_R = 0b100000 9 | _Input0_ZL = 0b1000000 10 | _Input0_ZR = 0b10000000 11 | 12 | _Input1_Minus = 0b1 13 | _Input1_Plus = 0b10 14 | _Input1_LPress = 0b100 15 | _Input1_RPress = 0b1000 16 | _Input1_Home = 0b10000 17 | _Input1_Capture = 0b100000 18 | 19 | _Input2_DPadTop = 0 20 | _Input2_DPadTopRight = 1 21 | _Input2_DPadRight = 2 22 | _Input2_DPadBottomRight = 3 23 | _Input2_DPadBottom = 4 24 | _Input2_DPadBottomLeft = 5 25 | _Input2_DPadLeft = 6 26 | _Input2_DPadTopLeft = 7 27 | _Input2_DPadCenter = 8 28 | 29 | class JoyStickInput_HORI_S(JoyStickInput): 30 | def __init__(self, input_line): 31 | self._buffer = bytearray(8) 32 | self._buffer[2] = _Input2_DPadCenter 33 | self._buffer[3] = 128 34 | self._buffer[4] = 128 35 | self._buffer[5] = 128 36 | self._buffer[6] = 128 37 | 38 | splits = input_line.upper().split("|", -1) 39 | for s in splits: 40 | s = s.strip() 41 | if s == "Y": 42 | self._buffer[0] |= _Input0_Y 43 | elif s == "B": 44 | self._buffer[0] |= _Input0_B 45 | elif s == "X": 46 | self._buffer[0] |= _Input0_X 47 | elif s == "A": 48 | self._buffer[0] |= _Input0_A 49 | elif s == "L": 50 | self._buffer[0] |= _Input0_L 51 | elif s == "R": 52 | self._buffer[0] |= _Input0_R 53 | elif s == "ZL": 54 | self._buffer[0] |= _Input0_ZL 55 | elif s == "ZR": 56 | self._buffer[0] |= _Input0_ZR 57 | elif s == "MINUS": 58 | self._buffer[1] |= _Input1_Minus 59 | elif s == "PLUS": 60 | self._buffer[1] |= _Input1_Plus 61 | elif s == "LPRESS": 62 | self._buffer[1] |= _Input1_LPress 63 | elif s == "RPRESS": 64 | self._buffer[1] |= _Input1_RPress 65 | elif s == "HOME": 66 | self._buffer[1] |= _Input1_Home 67 | elif s == "CAPTURE": 68 | self._buffer[1] |= _Input1_Capture 69 | elif s == "CENTER": 70 | self._buffer[2] = _Input2_DPadCenter 71 | elif s == "TOP": 72 | if self._buffer[2] != _Input2_DPadCenter: 73 | self._buffer[2] = _Input2_DPadCenter 74 | else: 75 | self._buffer[2] = _Input2_DPadTop 76 | elif s == "TOPRIGHT": 77 | if self._buffer[2] != _Input2_DPadCenter: 78 | self._buffer[2] = _Input2_DPadCenter 79 | else: 80 | self._buffer[2] = _Input2_DPadTopRight 81 | elif s == "RIGHT": 82 | if self._buffer[2] != _Input2_DPadCenter: 83 | self._buffer[2] = _Input2_DPadCenter 84 | else: 85 | self._buffer[2] = _Input2_DPadRight 86 | elif s == "BOTTOMRIGHT": 87 | if self._buffer[2] != _Input2_DPadCenter: 88 | self._buffer[2] = _Input2_DPadCenter 89 | else: 90 | self._buffer[2] = _Input2_DPadBottomRight 91 | elif s == "BOTTOM": 92 | if self._buffer[2] != _Input2_DPadCenter: 93 | self._buffer[2] = _Input2_DPadCenter 94 | else: 95 | self._buffer[2] = _Input2_DPadBottom 96 | elif s == "BOTTOMLEFT": 97 | if self._buffer[2] != _Input2_DPadCenter: 98 | self._buffer[2] = _Input2_DPadCenter 99 | else: 100 | self._buffer[2] = _Input2_DPadBottomLeft 101 | elif s == "LEFT": 102 | if self._buffer[2] != _Input2_DPadCenter: 103 | self._buffer[2] = _Input2_DPadCenter 104 | else: 105 | self._buffer[2] = _Input2_DPadLeft 106 | elif s == "TOPLEFT": 107 | if self._buffer[2] != _Input2_DPadCenter: 108 | self._buffer[2] = _Input2_DPadCenter 109 | else: 110 | self._buffer[2] = _Input2_DPadTopLeft 111 | else: 112 | stick = s.split("@", -1) 113 | if len(stick) == 2: 114 | x = 0 115 | y = 0 116 | coordinate = stick[1].split(",", -1) 117 | if len(coordinate) == 2: 118 | x = self._coordinate_str_convert_int(coordinate[0]) 119 | y = self._coordinate_str_convert_int(coordinate[1]) 120 | x += 128 121 | y += 128 122 | if stick[0] == "LSTICK": 123 | if self._buffer[3] == 128 and self._buffer[4] == 128: 124 | self._buffer[3] = x 125 | self._buffer[4] = y 126 | elif stick[0] == "RSTICK": 127 | if self._buffer[5] == 128 and self._buffer[6] == 128: 128 | self._buffer[5] = x 129 | self._buffer[6] = y 130 | 131 | def _coordinate_str_convert_int(self, str): 132 | v = 0 133 | try: 134 | v = int(float(str)) 135 | except: 136 | pass 137 | if v < -128: 138 | v = -128 139 | elif v > 127: 140 | v = 127 141 | return v 142 | 143 | def buffer(self): 144 | return self._buffer -------------------------------------------------------------------------------- /src/lib/adafruit_hid/keyboard_layout_us.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid.keyboard_layout_us.KeyboardLayoutUS` 7 | ======================================================= 8 | 9 | * Author(s): Dan Halbert 10 | """ 11 | 12 | from .keyboard_layout_base import KeyboardLayoutBase 13 | 14 | 15 | class KeyboardLayoutUS(KeyboardLayoutBase): 16 | """Map ASCII characters to appropriate keypresses on a standard US PC keyboard. 17 | 18 | Non-ASCII characters and most control characters will raise an exception. 19 | """ 20 | 21 | # The ASCII_TO_KEYCODE bytes object is used as a table to maps ASCII 0-127 22 | # to the corresponding # keycode on a US 104-key keyboard. 23 | # The user should not normally need to use this table, 24 | # but it is not marked as private. 25 | # 26 | # Because the table only goes to 127, we use the top bit of each byte (ox80) to indicate 27 | # that the shift key should be pressed. So any values 0x{8,9,a,b}* are shifted characters. 28 | # 29 | # The Python compiler will concatenate all these bytes literals into a single bytes object. 30 | # Micropython/CircuitPython will store the resulting bytes constant in flash memory 31 | # if it's in a .mpy file, so it doesn't use up valuable RAM. 32 | # 33 | # \x00 entries have no keyboard key and so won't be sent. 34 | ASCII_TO_KEYCODE = ( 35 | b"\x00" # NUL 36 | b"\x00" # SOH 37 | b"\x00" # STX 38 | b"\x00" # ETX 39 | b"\x00" # EOT 40 | b"\x00" # ENQ 41 | b"\x00" # ACK 42 | b"\x00" # BEL \a 43 | b"\x2a" # BS BACKSPACE \b (called DELETE in the usb.org document) 44 | b"\x2b" # TAB \t 45 | b"\x28" # LF \n (called Return or ENTER in the usb.org document) 46 | b"\x00" # VT \v 47 | b"\x00" # FF \f 48 | b"\x00" # CR \r 49 | b"\x00" # SO 50 | b"\x00" # SI 51 | b"\x00" # DLE 52 | b"\x00" # DC1 53 | b"\x00" # DC2 54 | b"\x00" # DC3 55 | b"\x00" # DC4 56 | b"\x00" # NAK 57 | b"\x00" # SYN 58 | b"\x00" # ETB 59 | b"\x00" # CAN 60 | b"\x00" # EM 61 | b"\x00" # SUB 62 | b"\x29" # ESC 63 | b"\x00" # FS 64 | b"\x00" # GS 65 | b"\x00" # RS 66 | b"\x00" # US 67 | b"\x2c" # SPACE 68 | b"\x9e" # ! x1e|SHIFT_FLAG (shift 1) 69 | b"\xb4" # " x34|SHIFT_FLAG (shift ') 70 | b"\xa0" # # x20|SHIFT_FLAG (shift 3) 71 | b"\xa1" # $ x21|SHIFT_FLAG (shift 4) 72 | b"\xa2" # % x22|SHIFT_FLAG (shift 5) 73 | b"\xa4" # & x24|SHIFT_FLAG (shift 7) 74 | b"\x34" # ' 75 | b"\xa6" # ( x26|SHIFT_FLAG (shift 9) 76 | b"\xa7" # ) x27|SHIFT_FLAG (shift 0) 77 | b"\xa5" # * x25|SHIFT_FLAG (shift 8) 78 | b"\xae" # + x2e|SHIFT_FLAG (shift =) 79 | b"\x36" # , 80 | b"\x2d" # - 81 | b"\x37" # . 82 | b"\x38" # / 83 | b"\x27" # 0 84 | b"\x1e" # 1 85 | b"\x1f" # 2 86 | b"\x20" # 3 87 | b"\x21" # 4 88 | b"\x22" # 5 89 | b"\x23" # 6 90 | b"\x24" # 7 91 | b"\x25" # 8 92 | b"\x26" # 9 93 | b"\xb3" # : x33|SHIFT_FLAG (shift ;) 94 | b"\x33" # ; 95 | b"\xb6" # < x36|SHIFT_FLAG (shift ,) 96 | b"\x2e" # = 97 | b"\xb7" # > x37|SHIFT_FLAG (shift .) 98 | b"\xb8" # ? x38|SHIFT_FLAG (shift /) 99 | b"\x9f" # @ x1f|SHIFT_FLAG (shift 2) 100 | b"\x84" # A x04|SHIFT_FLAG (shift a) 101 | b"\x85" # B x05|SHIFT_FLAG (etc.) 102 | b"\x86" # C x06|SHIFT_FLAG 103 | b"\x87" # D x07|SHIFT_FLAG 104 | b"\x88" # E x08|SHIFT_FLAG 105 | b"\x89" # F x09|SHIFT_FLAG 106 | b"\x8a" # G x0a|SHIFT_FLAG 107 | b"\x8b" # H x0b|SHIFT_FLAG 108 | b"\x8c" # I x0c|SHIFT_FLAG 109 | b"\x8d" # J x0d|SHIFT_FLAG 110 | b"\x8e" # K x0e|SHIFT_FLAG 111 | b"\x8f" # L x0f|SHIFT_FLAG 112 | b"\x90" # M x10|SHIFT_FLAG 113 | b"\x91" # N x11|SHIFT_FLAG 114 | b"\x92" # O x12|SHIFT_FLAG 115 | b"\x93" # P x13|SHIFT_FLAG 116 | b"\x94" # Q x14|SHIFT_FLAG 117 | b"\x95" # R x15|SHIFT_FLAG 118 | b"\x96" # S x16|SHIFT_FLAG 119 | b"\x97" # T x17|SHIFT_FLAG 120 | b"\x98" # U x18|SHIFT_FLAG 121 | b"\x99" # V x19|SHIFT_FLAG 122 | b"\x9a" # W x1a|SHIFT_FLAG 123 | b"\x9b" # X x1b|SHIFT_FLAG 124 | b"\x9c" # Y x1c|SHIFT_FLAG 125 | b"\x9d" # Z x1d|SHIFT_FLAG 126 | b"\x2f" # [ 127 | b"\x31" # \ backslash 128 | b"\x30" # ] 129 | b"\xa3" # ^ x23|SHIFT_FLAG (shift 6) 130 | b"\xad" # _ x2d|SHIFT_FLAG (shift -) 131 | b"\x35" # ` 132 | b"\x04" # a 133 | b"\x05" # b 134 | b"\x06" # c 135 | b"\x07" # d 136 | b"\x08" # e 137 | b"\x09" # f 138 | b"\x0a" # g 139 | b"\x0b" # h 140 | b"\x0c" # i 141 | b"\x0d" # j 142 | b"\x0e" # k 143 | b"\x0f" # l 144 | b"\x10" # m 145 | b"\x11" # n 146 | b"\x12" # o 147 | b"\x13" # p 148 | b"\x14" # q 149 | b"\x15" # r 150 | b"\x16" # s 151 | b"\x17" # t 152 | b"\x18" # u 153 | b"\x19" # v 154 | b"\x1a" # w 155 | b"\x1b" # x 156 | b"\x1c" # y 157 | b"\x1d" # z 158 | b"\xaf" # { x2f|SHIFT_FLAG (shift [) 159 | b"\xb1" # | x31|SHIFT_FLAG (shift \) 160 | b"\xb0" # } x30|SHIFT_FLAG (shift ]) 161 | b"\xb5" # ~ x35|SHIFT_FLAG (shift `) 162 | b"\x4c" # DEL DELETE (called Forward Delete in usb.org document) 163 | ) 164 | 165 | 166 | KeyboardLayout = KeyboardLayoutUS 167 | -------------------------------------------------------------------------------- /src/hid/device/switch_pro.py: -------------------------------------------------------------------------------- 1 | import usb_hid 2 | from adafruit_hid import find_device 3 | from hid.device import device 4 | 5 | Tag = "PRO CONTROLLER" 6 | 7 | _PRO_CONTROLLER_DESCRIPTOR = bytes(( 8 | # HID Descriptor 9 | 10 | 0x05, 0x01, # Usage Page (Generic Desktop Ctrls) 11 | 0x15, 0x00, # Logical Minimum (0) 12 | 0x09, 0x04, # Usage (Joystick) 13 | 0xA1, 0x01, # Collection (Application) 14 | 0x85, 0x30, # Report ID (48) 15 | 0x05, 0x01, # Usage Page (Generic Desktop Ctrls) 16 | 0x05, 0x09, # Usage Page (Button) 17 | 0x19, 0x01, # Usage Minimum (0x01) 18 | 0x29, 0x0A, # Usage Maximum (0x0A) 19 | 0x15, 0x00, # Logical Minimum (0) 20 | 0x25, 0x01, # Logical Maximum (1) 21 | 0x75, 0x01, # Report Size (1) 22 | 0x95, 0x0A, # Report Count (10) 23 | 0x55, 0x00, # Unit Exponent (0) 24 | 0x65, 0x00, # Unit (None) 25 | 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 26 | 0x05, 0x09, # Usage Page (Button) 27 | 0x19, 0x0B, # Usage Minimum (0x0B) 28 | 0x29, 0x0E, # Usage Maximum (0x0E) 29 | 0x15, 0x00, # Logical Minimum (0) 30 | 0x25, 0x01, # Logical Maximum (1) 31 | 0x75, 0x01, # Report Size (1) 32 | 0x95, 0x04, # Report Count (4) 33 | 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 34 | 0x75, 0x01, # Report Size (1) 35 | 0x95, 0x02, # Report Count (2) 36 | 0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 37 | 0x0B, 0x01, 0x00, 0x01, 0x00, # Usage (0x010001) 38 | 0xA1, 0x00, # Collection (Physical) 39 | 0x0B, 0x30, 0x00, 0x01, 0x00, # Usage (0x010030) 40 | 0x0B, 0x31, 0x00, 0x01, 0x00, # Usage (0x010031) 41 | 0x0B, 0x32, 0x00, 0x01, 0x00, # Usage (0x010032) 42 | 0x0B, 0x35, 0x00, 0x01, 0x00, # Usage (0x010035) 43 | 0x15, 0x00, # Logical Minimum (0) 44 | 0x27, 0xFF, 0xFF, 0x00, 0x00, # Logical Maximum (65534) 45 | 0x75, 0x10, # Report Size (16) 46 | 0x95, 0x04, # Report Count (4) 47 | 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 48 | 0xC0, # End Collection 49 | 0x0B, 0x39, 0x00, 0x01, 0x00, # Usage (0x010039) 50 | 0x15, 0x00, # Logical Minimum (0) 51 | 0x25, 0x07, # Logical Maximum (7) 52 | 0x35, 0x00, # Physical Minimum (0) 53 | 0x46, 0x3B, 0x01, # Physical Maximum (315) 54 | 0x65, 0x14, # Unit (System: English Rotation, Length: Centimeter) 55 | 0x75, 0x04, # Report Size (4) 56 | 0x95, 0x01, # Report Count (1) 57 | 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 58 | 0x05, 0x09, # Usage Page (Button) 59 | 0x19, 0x0F, # Usage Minimum (0x0F) 60 | 0x29, 0x12, # Usage Maximum (0x12) 61 | 0x15, 0x00, # Logical Minimum (0) 62 | 0x25, 0x01, # Logical Maximum (1) 63 | 0x75, 0x01, # Report Size (1) 64 | 0x95, 0x04, # Report Count (4) 65 | 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 66 | 0x75, 0x08, # Report Size (8) 67 | 0x95, 0x34, # Report Count (52) 68 | 0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 69 | 0x06, 0x00, 0xFF, # Usage Page (Vendor Defined 0xFF00) 70 | 0x85, 0x21, # Report ID (33) 71 | 0x09, 0x01, # Usage (0x01) 72 | 0x75, 0x08, # Report Size (8) 73 | 0x95, 0x3F, # Report Count (63) 74 | 0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 75 | 0x85, 0x81, # Report ID (-127) 76 | 0x09, 0x02, # Usage (0x02) 77 | 0x75, 0x08, # Report Size (8) 78 | 0x95, 0x3F, # Report Count (63) 79 | 0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 80 | 0x85, 0x01, # Report ID (1) 81 | 0x09, 0x03, # Usage (0x03) 82 | 0x75, 0x08, # Report Size (8) 83 | 0x95, 0x3F, # Report Count (63) 84 | 0x91, 0x83, # Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile) 85 | 0x85, 0x10, # Report ID (16) 86 | 0x09, 0x04, # Usage (0x04) 87 | 0x75, 0x08, # Report Size (8) 88 | 0x95, 0x3F, # Report Count (63) 89 | 0x91, 0x83, # Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile) 90 | 0x85, 0x80, # Report ID (-128) 91 | 0x09, 0x05, # Usage (0x05) 92 | 0x75, 0x08, # Report Size (8) 93 | 0x95, 0x3F, # Report Count (63) 94 | 0x91, 0x83, # Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile) 95 | 0x85, 0x82, # Report ID (-126) 96 | 0x09, 0x06, # Usage (0x06) 97 | 0x75, 0x08, # Report Size (8) 98 | 0x95, 0x3F, # Report Count (63) 99 | 0x91, 0x83, # Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile) 100 | 0xC0, # End Collection 101 | )) 102 | 103 | SwitchPro = usb_hid.Device( 104 | report_descriptor=_PRO_CONTROLLER_DESCRIPTOR, 105 | usage_page=0x01, 106 | usage=0x04, 107 | report_ids=(0x30,0x21,0x81,0x01,0x10,0x80), 108 | in_report_lengths=(63,63,63,0,0,0), 109 | out_report_lengths=(0,0,0,63,63,63), 110 | ) 111 | 112 | class Device_Switch_Pro(device.Device): 113 | def __init__(self): 114 | super().__init__() 115 | 116 | def init_device(self): 117 | try: 118 | import supervisor 119 | supervisor.set_usb_identification("Nintendo Co., Ltd.","Pro Controller",0x057E,0x2009) 120 | except: 121 | pass 122 | usb_hid.enable((SwitchPro,)) 123 | 124 | def find_device(self): 125 | return find_device( 126 | usb_hid.devices, usage_page=0x01, usage=0x04) -------------------------------------------------------------------------------- /src/lib/adafruit_ticks.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries 3 | # 4 | # SPDX-License-Identifier: MIT 5 | """ 6 | `adafruit_ticks` 7 | ================================================================================ 8 | 9 | Work with intervals and deadlines in milliseconds 10 | 11 | 12 | * Author(s): Jeff Epler 13 | 14 | Implementation Notes 15 | -------------------- 16 | 17 | **Software and Dependencies:** 18 | 19 | * Adafruit CircuitPython firmware for the supported boards: 20 | https://github.com/adafruit/circuitpython/releases 21 | 22 | """ 23 | 24 | # imports 25 | from micropython import const 26 | 27 | __version__ = "1.0.11" 28 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ticks.git" 29 | 30 | _TICKS_PERIOD = const(1 << 29) 31 | _TICKS_MAX = const(_TICKS_PERIOD - 1) 32 | _TICKS_HALFPERIOD = const(_TICKS_PERIOD // 2) 33 | 34 | # Get the correct implementation of ticks_ms. There are three possibilities: 35 | # 36 | # - supervisor.ticks_ms is present. This will be the case starting in CP7.0 37 | # 38 | # - time.ticks_ms is present. This is the case for MicroPython & for the "unix 39 | # port" of CircuitPython, used for some automated testing. 40 | # 41 | # - time.monotonic_ns is present, and works. This is the case on most 42 | # Express boards in CP6.x, and most host computer versions of Python. 43 | # 44 | # - Otherwise, time.monotonic is assumed to be present. This is the case 45 | # on most non-express boards in CP6.x, and some old host computer versions 46 | # of Python. 47 | # 48 | # Note that on microcontrollers, this time source becomes increasingly 49 | # inaccurate when the board has not been reset in a long time, losing the 50 | # ability to measure 1ms intervals after about 1 hour, and losing the 51 | # ability to meausre 128ms intervals after 6 days. The only solution is to 52 | # either upgrade to a version with supervisor.ticks_ms, or to switch to a 53 | # board with time.monotonic_ns. 54 | 55 | try: 56 | from supervisor import ticks_ms # pylint: disable=unused-import 57 | except (ImportError, NameError): 58 | import time 59 | 60 | if _ticks_ms := getattr(time, "ticks_ms", None): 61 | 62 | def ticks_ms() -> int: 63 | """Return the time in milliseconds since an unspecified moment, 64 | wrapping after 2**29ms. 65 | 66 | The wrap value was chosen so that it is always possible to add or 67 | subtract two `ticks_ms` values without overflow on a board without 68 | long ints (or without allocating any long integer objects, on 69 | boards with long ints). 70 | 71 | This ticks value comes from a low-accuracy clock internal to the 72 | microcontroller, just like `time.monotonic`. Due to its low 73 | accuracy and the fact that it "wraps around" every few days, it is 74 | intended for working with short term events like advancing an LED 75 | animation, not for long term events like counting down the time 76 | until a holiday.""" 77 | return _ticks_ms() & _TICKS_MAX # pylint: disable=not-callable 78 | 79 | else: 80 | try: 81 | from time import monotonic_ns as _monotonic_ns 82 | 83 | _monotonic_ns() # Check that monotonic_ns is usable 84 | 85 | def ticks_ms() -> int: 86 | """Return the time in milliseconds since an unspecified moment, 87 | wrapping after 2**29ms. 88 | 89 | The wrap value was chosen so that it is always possible to add or 90 | subtract two `ticks_ms` values without overflow on a board without 91 | long ints (or without allocating any long integer objects, on 92 | boards with long ints). 93 | 94 | This ticks value comes from a low-accuracy clock internal to the 95 | microcontroller, just like `time.monotonic`. Due to its low 96 | accuracy and the fact that it "wraps around" every few days, it is 97 | intended for working with short term events like advancing an LED 98 | animation, not for long term events like counting down the time 99 | until a holiday.""" 100 | return (_monotonic_ns() // 1_000_000) & _TICKS_MAX 101 | 102 | except (ImportError, NameError, NotImplementedError): 103 | from time import monotonic as _monotonic 104 | 105 | def ticks_ms() -> int: 106 | """Return the time in milliseconds since an unspecified moment, 107 | wrapping after 2**29ms. 108 | 109 | The wrap value was chosen so that it is always possible to add or 110 | subtract two `ticks_ms` values without overflow on a board without 111 | long ints (or without allocating any long integer objects, on 112 | boards with long ints). 113 | 114 | This ticks value comes from a low-accuracy clock internal to the 115 | microcontroller, just like `time.monotonic`. Due to its low 116 | accuracy and the fact that it "wraps around" every few days, it is 117 | intended for working with short term events like advancing an LED 118 | animation, not for long term events like counting down the time 119 | until a holiday.""" 120 | return int(_monotonic() * 1000) & _TICKS_MAX 121 | 122 | 123 | def ticks_add(ticks: int, delta: int) -> int: 124 | "Add a delta to a base number of ticks, performing wraparound at 2**29ms." 125 | return (ticks + delta) % _TICKS_PERIOD 126 | 127 | 128 | def ticks_diff(ticks1: int, ticks2: int) -> int: 129 | """Compute the signed difference between two ticks values, 130 | assuming that they are within 2**28 ticks""" 131 | diff = (ticks1 - ticks2) & _TICKS_MAX 132 | diff = ((diff + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD 133 | return diff 134 | 135 | 136 | def ticks_less(ticks1: int, ticks2: int) -> bool: 137 | """Return true if ticks1 is before ticks2 and false otherwise, 138 | assuming that they are within 2**28 ticks""" 139 | return ticks_diff(ticks1, ticks2) < 0 140 | -------------------------------------------------------------------------------- /src/macros/__init__.py: -------------------------------------------------------------------------------- 1 | from macros import macro,action 2 | import hid.joystick 3 | import time 4 | import asyncio 5 | import json 6 | import customize.config as config 7 | import customize.task_manager as task_manager 8 | 9 | joystick = hid.joystick.JoyStickFactory.get_instance() 10 | 11 | TASK_TAG: str = "macros" 12 | _macro_running: bool = False 13 | _realtime_running: bool = False 14 | _current_info = "" 15 | _result_info = "" 16 | _start_time = None 17 | _action_queue = [] 18 | 19 | def status_info(): 20 | global _start_time 21 | global _result_info 22 | if _start_time != None and _start_time != 0: 23 | return current_info() 24 | elif _result_info != None and _result_info != "": 25 | return _result_info 26 | else: 27 | result_info() 28 | 29 | 30 | def current_info(): 31 | global _current_info 32 | global _start_time 33 | global _macro_running 34 | if _start_time == None or _start_time == 0: 35 | return "没有正在执行的脚本" 36 | if not _macro_running : 37 | return "已收到中止指令,正在处理中" 38 | span = int(time.time() - _start_time) 39 | return _current_info + "持续运行时间:{:.0f}小时{:.0f}分{:.0f}秒".format(span/3600, (span % 3600)/60, span % 60) 40 | 41 | 42 | def result_info(): 43 | global _result_info 44 | return _result_info 45 | 46 | def add_joystick_task(cmd: str): 47 | try: 48 | return _create_task_json(json.loads(cmd)) 49 | except: 50 | global _result_info 51 | _result_info = "启动命令{}有错误,请检查。".format(cmd) 52 | return _result_info 53 | 54 | def macro_stop(): 55 | global _macro_running 56 | _macro_running = False 57 | global _action_queue 58 | _action_queue = [] 59 | 60 | 61 | def action_queue_task_start(): 62 | tm = task_manager.TaskManager() 63 | tm.create_task(_run_queue(), TASK_TAG) 64 | add_joystick_task('{"name":"common.wakeup_joystick","loop":1}') 65 | 66 | 67 | def auto_run(): 68 | c = config.Config() 69 | try: 70 | _create_task_json(c.autorun) 71 | except: 72 | global _result_info 73 | _result_info = "Config文件macro.autorun节点存在错误,无法启动脚本" 74 | 75 | 76 | def _create_task_json(cmd: dict): 77 | global _action_queue 78 | realtime_action = cmd.get("realtime") 79 | if type(realtime_action) is str: 80 | global _realtime_running 81 | if realtime_action == "action_start": 82 | macro_stop() 83 | _realtime_running = True 84 | joystick.start_realtime() 85 | return "开始实时控制模式" 86 | elif realtime_action == "action_stop": 87 | _realtime_running = False 88 | joystick.stop_realtime() 89 | return "结束实时控制模式" 90 | else: 91 | if _realtime_running: 92 | _action_queue.append((realtime_action,)) 93 | return 94 | if _realtime_running: 95 | return 96 | s = cmd.get("stop") 97 | c1 = cmd.get("name") 98 | c2 = cmd.get("loop") 99 | paras = cmd.get("paras") 100 | if type(s) is bool and s: 101 | macro_stop() 102 | if c1==None: 103 | return "停止脚本" 104 | if type(c1) is str: 105 | name = c1 106 | else: 107 | raise 108 | 109 | if type(c2) is int: 110 | loop = c2 111 | else: 112 | loop = -1 113 | try: 114 | _action_queue.append((name, loop, paras)) 115 | return "{}:已添加任务。".format((name, loop, paras)) 116 | except: 117 | return "任务队列已满。".format((name, loop, paras)) 118 | 119 | 120 | def published(): 121 | m= macro.Macro() 122 | if m._publish!= None: 123 | return json.dumps(m._publish, separators=(',', ':')) 124 | else: 125 | return "" 126 | 127 | async def _run_queue(): 128 | global _realtime_running 129 | while True: 130 | t = None 131 | global _action_queue 132 | if len(_action_queue) > 0: 133 | t = _action_queue[0] 134 | _action_queue.remove(t) 135 | if _realtime_running: 136 | if t == None or len(t)!=1: 137 | await asyncio.sleep_ms(1) 138 | continue 139 | await joystick.send_realtime_action(t[0]) 140 | else: 141 | if t == None or len(t)<3: 142 | await asyncio.sleep_ms(10) 143 | continue 144 | await _run(t[0],t[1],t[2]) 145 | 146 | 147 | async def _run(name: str, loop: int = 1, paras: dict = dict()): 148 | msg = "开始运行{}脚本,循环次数:{}".format(name, loop) 149 | print(msg) 150 | times = 0 151 | if loop <= 0: 152 | loop = -1 153 | global _macro_running 154 | global _current_info 155 | global _result_info 156 | global _start_time 157 | start_ts = time.time() 158 | _start_time = start_ts 159 | try: 160 | act = _get_action(name,paras) 161 | if act == None: 162 | _result_info = "不存在名称为{}的脚本".format(name) 163 | return 164 | _macro_running = True 165 | _result_info = "" 166 | _current_info = "正在运行[{}]脚本,已运行{}次,计划运行{}次\n".format( 167 | name, times, loop) 168 | while True: 169 | while True: 170 | if not _macro_running: 171 | raise asyncio.CancelledError 172 | ret = act.pop() 173 | # print(ret[0]) 174 | if ret[0] != None: 175 | await joystick.do_action(ret[0]) 176 | if ret[1]: 177 | break 178 | times += 1 179 | _current_info = "正在运行[{}]脚本,已运行{}次,计划运行{}次\n".format( 180 | name, times, loop) 181 | if loop > 0 and times >= loop: 182 | break 183 | act.cycle_reset() 184 | msg = "脚本{}运行完成,当前运行次数:{}".format(name, times) 185 | print(msg) 186 | span = time.time() - start_ts 187 | _result_info = "脚本[{}]运行完成,实际运行{}次\n持续运行时间:{:.0f}小时{:.0f}分{:.0f}秒".format( 188 | name, times, span/3600, (span % 3600)/60, span % 60) 189 | except asyncio.CancelledError: 190 | await joystick.release() 191 | msg = "脚本{}运行中止,当前运行次数:{}".format(name, times) 192 | print(msg) 193 | span = time.time() - start_ts 194 | _result_info = "脚本[{}]停止,实际运行{}次,计划运行{}次\n持续运行时间:{:.0f}小时{:.0f}分{:.0f}秒".format( 195 | name, times, loop, span/3600, (span % 3600)/60, span % 60) 196 | finally: 197 | _start_time = None 198 | _macro_running = False 199 | _current_info = "" 200 | 201 | 202 | def _get_action(name: str,paras:dict=dict()) -> action.Action: 203 | act = action.Action(name,paras) 204 | if act._head == None: 205 | return None 206 | return act 207 | -------------------------------------------------------------------------------- /src/lib/asyncio/task.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019-2020 Damien P. George 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # 5 | # MicroPython uasyncio module 6 | # MIT license; Copyright (c) 2019-2020 Damien P. George 7 | # 8 | # This code comes from MicroPython, and has not been run through black or pylint there. 9 | # Altering these files significantly would make merging difficult, so we will not use 10 | # pylint or black. 11 | # pylint: skip-file 12 | # fmt: off 13 | """ 14 | Tasks 15 | ===== 16 | """ 17 | 18 | # This file contains the core TaskQueue based on a pairing heap, and the core Task class. 19 | # They can optionally be replaced by C implementations. 20 | 21 | from . import core 22 | 23 | 24 | # pairing-heap meld of 2 heaps; O(1) 25 | def ph_meld(h1, h2): 26 | if h1 is None: 27 | return h2 28 | if h2 is None: 29 | return h1 30 | lt = core.ticks_diff(h1.ph_key, h2.ph_key) < 0 31 | if lt: 32 | if h1.ph_child is None: 33 | h1.ph_child = h2 34 | else: 35 | h1.ph_child_last.ph_next = h2 36 | h1.ph_child_last = h2 37 | h2.ph_next = None 38 | h2.ph_rightmost_parent = h1 39 | return h1 40 | else: 41 | h1.ph_next = h2.ph_child 42 | h2.ph_child = h1 43 | if h1.ph_next is None: 44 | h2.ph_child_last = h1 45 | h1.ph_rightmost_parent = h2 46 | return h2 47 | 48 | 49 | # pairing-heap pairing operation; amortised O(log N) 50 | def ph_pairing(child): 51 | heap = None 52 | while child is not None: 53 | n1 = child 54 | child = child.ph_next 55 | n1.ph_next = None 56 | if child is not None: 57 | n2 = child 58 | child = child.ph_next 59 | n2.ph_next = None 60 | n1 = ph_meld(n1, n2) 61 | heap = ph_meld(heap, n1) 62 | return heap 63 | 64 | 65 | # pairing-heap delete of a node; stable, amortised O(log N) 66 | def ph_delete(heap, node): 67 | if node is heap: 68 | child = heap.ph_child 69 | node.ph_child = None 70 | return ph_pairing(child) 71 | # Find parent of node 72 | parent = node 73 | while parent.ph_next is not None: 74 | parent = parent.ph_next 75 | parent = parent.ph_rightmost_parent 76 | # Replace node with pairing of its children 77 | if node is parent.ph_child and node.ph_child is None: 78 | parent.ph_child = node.ph_next 79 | node.ph_next = None 80 | return heap 81 | elif node is parent.ph_child: 82 | child = node.ph_child 83 | next = node.ph_next 84 | node.ph_child = None 85 | node.ph_next = None 86 | node = ph_pairing(child) 87 | parent.ph_child = node 88 | else: 89 | n = parent.ph_child 90 | while node is not n.ph_next: 91 | n = n.ph_next 92 | child = node.ph_child 93 | next = node.ph_next 94 | node.ph_child = None 95 | node.ph_next = None 96 | node = ph_pairing(child) 97 | if node is None: 98 | node = n 99 | else: 100 | n.ph_next = node 101 | node.ph_next = next 102 | if next is None: 103 | node.ph_rightmost_parent = parent 104 | parent.ph_child_last = node 105 | return heap 106 | 107 | 108 | # TaskQueue class based on the above pairing-heap functions. 109 | class TaskQueue: 110 | def __init__(self): 111 | self.heap = None 112 | 113 | def peek(self): 114 | return self.heap 115 | 116 | def push_sorted(self, v, key): 117 | v.data = None 118 | v.ph_key = key 119 | v.ph_child = None 120 | v.ph_next = None 121 | self.heap = ph_meld(v, self.heap) 122 | 123 | def push_head(self, v): 124 | self.push_sorted(v, core.ticks()) 125 | 126 | def pop_head(self): 127 | v = self.heap 128 | self.heap = ph_pairing(self.heap.ph_child) 129 | return v 130 | 131 | def remove(self, v): 132 | self.heap = ph_delete(self.heap, v) 133 | 134 | 135 | # Task class representing a coroutine, can be waited on and cancelled. 136 | class Task: 137 | """This object wraps a coroutine into a running task. Tasks can be waited on 138 | using ``await task``, which will wait for the task to complete and return the 139 | return value of the task. 140 | 141 | Tasks should not be created directly, rather use ``create_task`` to create them. 142 | """ 143 | 144 | def __init__(self, coro, globals=None): 145 | self.coro = coro # Coroutine of this Task 146 | self.data = None # General data for queue it is waiting on 147 | self.state = True # None, False, True or a TaskQueue instance 148 | self.ph_key = 0 # Pairing heap 149 | self.ph_child = None # Paring heap 150 | self.ph_child_last = None # Paring heap 151 | self.ph_next = None # Paring heap 152 | self.ph_rightmost_parent = None # Paring heap 153 | 154 | def __iter__(self): 155 | if not self.state: 156 | # Task finished, signal that is has been await'ed on. 157 | self.state = False 158 | elif self.state is True: 159 | # Allocated head of linked list of Tasks waiting on completion of this task. 160 | self.state = TaskQueue() 161 | return self 162 | 163 | __await__ = __iter__ 164 | 165 | def __next__(self): 166 | if not self.state: 167 | # Task finished, raise return value to caller so it can continue. 168 | raise self.data 169 | else: 170 | # Put calling task on waiting queue. 171 | self.state.push_head(core.cur_task) 172 | # Set calling task's data to this task that it waits on, to double-link it. 173 | core.cur_task.data = self 174 | 175 | def done(self): 176 | """Whether the task is complete.""" 177 | 178 | return not self.state 179 | 180 | def cancel(self): 181 | """Cancel the task by injecting a ``CancelledError`` into it. The task 182 | may or may not ignore this exception. 183 | """ 184 | 185 | # Check if task is already finished. 186 | if not self.state: 187 | return False 188 | # Can't cancel self (not supported yet). 189 | if self is core.cur_task: 190 | raise RuntimeError("can't cancel self") 191 | # If Task waits on another task then forward the cancel to the one it's waiting on. 192 | while isinstance(self.data, Task): 193 | self = self.data 194 | # Reschedule Task as a cancelled task. 195 | if hasattr(self.data, "remove"): 196 | # Not on the main running queue, remove the task from the queue it's on. 197 | self.data.remove(self) 198 | core._task_queue.push_head(self) 199 | elif core.ticks_diff(self.ph_key, core.ticks()) > 0: 200 | # On the main running queue but scheduled in the future, so bring it forward to now. 201 | core._task_queue.remove(self) 202 | core._task_queue.push_head(self) 203 | self.data = core.CancelledError 204 | return True 205 | -------------------------------------------------------------------------------- /src/macros/macro.py: -------------------------------------------------------------------------------- 1 | from macros import node 2 | import os 3 | import io 4 | import time 5 | import random 6 | 7 | random.seed(time.time()) 8 | _S_IFDIR = const(16384) 9 | _MACRO_BASE_PATH = "/resources/macros" 10 | _MACRO_EXT = ".m" 11 | _FINISHED_LINE = "0000000" 12 | 13 | class Macro(object): 14 | def __new__(cls, *args, **kwargs): 15 | if not hasattr(cls, '_instance'): 16 | cls._instance = super(Macro, cls).__new__(cls) 17 | return cls._instance 18 | 19 | _first = True 20 | 21 | def __init__(self): 22 | if Macro._first: 23 | Macro._first = False 24 | self._publish = [] 25 | self._default_paras = dict() 26 | self._dic_macros = self._get_macros() 27 | 28 | def get_node(self, name: str) -> node.Node: 29 | try: 30 | macro_name = name 31 | for p in self._publish: 32 | if p.get("summary") == name: 33 | macro_name = p.get("name") 34 | return (self._dic_macros.get(macro_name),self._default_paras.get(macro_name)) 35 | except: 36 | return None 37 | 38 | def _get_macros(self) -> dict: 39 | dic_action_lines = dict() 40 | macros = self._walk_macro_files() 41 | for macro in macros: 42 | dic_action_lines = self._load_file(macro, dic_action_lines) 43 | dic_macros = dict() 44 | for key in dic_action_lines.keys(): 45 | rows = dic_action_lines.get(key) 46 | if rows != None and len(rows) < 1: 47 | continue 48 | action = None 49 | for row in dic_action_lines.get(key): 50 | if action == None: 51 | action = node.Node(row) 52 | else: 53 | action = action.append(row) 54 | dic_macros[key] = action.head 55 | return dic_macros 56 | 57 | def _walk_macro_files(self, base=_MACRO_BASE_PATH): 58 | ret = [] 59 | for p in os.listdir(base): 60 | path = "{}/{}".format(base, p) 61 | if p.startswith('.'): 62 | continue 63 | elif p.lower().endswith(_MACRO_EXT): 64 | ret.append(path) 65 | elif (os.stat(path)[0] & _S_IFDIR): 66 | ret.extend(self._walk_macro_files(path)) 67 | return ret 68 | 69 | def _load_file(self, filename, dic=dict()) -> dict: 70 | try: 71 | f = open(filename, "rt") 72 | except: 73 | return dic 74 | 75 | file_tag = filename[len(_MACRO_BASE_PATH) + 1:( 76 | len(filename) - len(_MACRO_EXT))].replace("/", ".", -1) + "." 77 | rows = [] 78 | while True: 79 | row = f.readline() 80 | if row == "": 81 | break 82 | row = row.strip() 83 | if row == "": 84 | continue 85 | elif row.startswith("--") or row.startswith("#"): 86 | continue 87 | row = row.replace(" ", "", -1).replace("\t", "", -1) 88 | if row.startswith("[") and row.count(".") == 0: 89 | row = "[" + file_tag + row[1:] 90 | rows.append(row) 91 | if len(rows) > 0: 92 | dic = self._read_segments(rows, dic=dic, file_tag=file_tag) 93 | f.close() 94 | return dic 95 | 96 | def _read_segments(self, src_rows, dic=dict(), file_tag: str = "", name: str = "") -> dict: 97 | rows = [] 98 | sub_rows = [] 99 | sub = False 100 | sub_floor = 0 101 | for row in src_rows: 102 | if row.startswith("<") and row.endswith(">"): 103 | if len(rows) > 0: 104 | if name != "": 105 | rows.append(_FINISHED_LINE) 106 | dic[name] = rows 107 | rows = [] 108 | name = self._process_title(row[1:len(row) - 1],file_tag) 109 | sub_rows = [] 110 | sub = False 111 | continue 112 | elif sub: 113 | pass 114 | elif row.startswith("{"): 115 | sub = True 116 | row = row[1:] 117 | if sub: 118 | str_io = io.StringIO() 119 | for index in range(0, len(row)): 120 | if row[index] == "}" and sub_floor == 0: 121 | sub_row = str_io.getvalue() 122 | if len(sub_row) > 0: 123 | sub_rows.append(sub_row) 124 | if len(sub_rows) == 0: 125 | sub = False 126 | break 127 | sub_name = hex(random.randint(1, time.time())) 128 | ends = "" 129 | if index < len(row) - 1: 130 | ends = row[index + 1:] 131 | line = "[{}]{}".format(sub_name, ends) 132 | rows.append(line) 133 | dic = self._read_segments(sub_rows, dic=dic, 134 | file_tag="", name=sub_name) 135 | sub_rows = [] 136 | sub = False 137 | break 138 | elif row[index] == "}" and sub_floor > 0: 139 | sub_floor -= 1 140 | elif row[index] == "{": 141 | sub_floor += 1 142 | str_io.write(row[index]) 143 | if sub: 144 | sub_row = str_io.getvalue() 145 | if len(sub_row) > 0: 146 | sub_rows.append(sub_row) 147 | str_io.close() 148 | else: 149 | rows.append(row) 150 | if len(rows) > 0 and name != "": 151 | rows.append(_FINISHED_LINE) 152 | dic[name] = rows 153 | return dic 154 | 155 | def _process_title(self, title_line:str,file_tag: str = "") -> str: 156 | splits = title_line.split("--") 157 | t1 = splits[0].split("|") 158 | name = file_tag + t1[0] 159 | summary = "" 160 | loop = -1 161 | paras = [] 162 | if t1[0] != "": 163 | if len(t1) >= 3: 164 | summary = t1[1] 165 | try: 166 | loop = int(float(t1[2])) 167 | except: 168 | pass 169 | elif len(t1) == 2: 170 | summary = t1[1] 171 | if len(splits) > 1: 172 | dic_paras = dict() 173 | for para in splits[1:]: 174 | p2 = "" 175 | v = "" 176 | s = para.split("|") 177 | p1 = s[0] 178 | if len(s)>= 3: 179 | p2 = s[1] 180 | v = s[2] 181 | elif len(s)==2: 182 | p2 =s[1] 183 | paras.append(dict({"name":p1,"summary":p2,"default":v})) 184 | dic_paras[p1] = v 185 | self._default_paras[name] = dic_paras 186 | 187 | if summary != "": 188 | self._publish.append(dict({"name":name,"summary":summary,"loop":loop,"paras":paras})) 189 | 190 | return name --------------------------------------------------------------------------------