diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 200872c..3587a77 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest + pip install flake8 pytest mypy # Add mypy here if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | @@ -34,6 +34,18 @@ jobs: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Generate and Test Stubs (stubtest) + run: | + if [ -d dobotWrapperPy ]; then # Check if the directory exists + pip install -e . + fi + + mkdir -p stubs/ + + stubgen -p dobotWrapperPy -o stubs/ + + stubtest dobotWrapperPy + - name: Test with pytest run: | pytest diff --git a/.gitignore b/.gitignore index e5d6a56..41643b0 100644 --- a/.gitignore +++ b/.gitignore @@ -175,3 +175,5 @@ cython_debug/ py_env test_docs + +stubs/ diff --git a/dobotWrapperPy/__init__.py b/dobotWrapperPy/__init__.py index 87b307d..4cb3052 100644 --- a/dobotWrapperPy/__init__.py +++ b/dobotWrapperPy/__init__.py @@ -1,6 +1,5 @@ -from .dobot import Dobot from .dobotapi import DobotApi from .dobotConnection import DobotConnection from .dobotasync import DobotAsync -__all__ = ["Dobot", "DobotApi", "DobotConnection", "DobotAsync"] +__all__ = ["DobotApi", "DobotConnection", "DobotAsync"] diff --git a/dobotWrapperPy/dobot.py b/dobotWrapperPy/dobot.py deleted file mode 100644 index 4064a91..0000000 --- a/dobotWrapperPy/dobot.py +++ /dev/null @@ -1,67 +0,0 @@ -from .dobotapi import DobotApi -from .dobotConnection import DobotConnection -import warnings -import struct -from .enums.ptpMode import PTPMode -from .message import Message -from .paramsStructures import ( - tagPTPCommonParams, - tagPTPCoordinateParams, - tagWAITCmd, - tagPose, -) -from typing import Tuple - - -class Dobot: - dobotApiInterface: DobotApi - - def __init__(self, port: str, verbose: bool = False) -> None: - conn = DobotConnection(port=port) - self.dobotApiInterface = DobotApi(conn, verbose) - - def __del__(self) -> None: - if hasattr(self, "dobotApiInterface") and self.dobotApiInterface is not None: - del self.dobotApiInterface - - def go(self, x: float, y: float, z: float, r: float = 0.0) -> None: - warnings.warn("go() is deprecated, use move_to() instead") - self.move_to(x, y, z, r) - - def move_to( - self, x: float, y: float, z: float, r: float, wait: bool = False - ) -> None: - self.dobotApiInterface.set_ptp_cmd(x, y, z, r, mode=PTPMode.MOVL_XYZ, wait=wait) - - def suck(self, enable: bool) -> None: - self.dobotApiInterface.set_end_effector_suction_cup(enable) - - def grip(self, enable: bool) -> None: - self.dobotApiInterface.set_end_effector_gripper(enable) - - def speed(self, velocity: float = 100.0, acceleration: float = 100.0) -> None: - self.dobotApiInterface.set_ptp_common_params( - tagPTPCommonParams(velocity, acceleration) - ) - self.dobotApiInterface.set_ptp_coordinate_params( - tagPTPCoordinateParams(velocity, velocity, acceleration, acceleration) - ) - - def wait(self, ms: int) -> None: - self.dobotApiInterface.set_wait_cmd(tagWAITCmd(ms)) - - def pose(self) -> Tuple[float, float, float, float, float, float, float, float]: - response: Message = self.dobotApiInterface.get_pose() - pos = tagPose.unpack(response.bytes()) - return ( - pos.x, - pos.y, - pos.z, - pos.r, - pos.jointAngle[0], - pos.jointAngle[1], - pos.jointAngle[2], - pos.jointAngle[3], - ) - - # TODO: Implement eio diff --git a/dobotWrapperPy/dobotConnection.py b/dobotWrapperPy/dobotConnection.py index 05f989f..f47d677 100644 --- a/dobotWrapperPy/dobotConnection.py +++ b/dobotWrapperPy/dobotConnection.py @@ -5,7 +5,7 @@ class DobotConnection: - serial_conn: serial.Serial + serial_conn: serial.SerialBase def __init__( self, port: Optional[str] = None, serial_conn: Optional[serial.Serial] = None diff --git a/dobotWrapperPy/dobotControlGUI.py b/dobotWrapperPy/dobotControlGUI.py index d4efbcd..1c92f48 100644 --- a/dobotWrapperPy/dobotControlGUI.py +++ b/dobotWrapperPy/dobotControlGUI.py @@ -1,7 +1,8 @@ from .dobotasync import DobotAsync import asyncio import tkinter as tk -from typing import Callable, Awaitable, Any, Dict +from typing import Callable +import typing import threading @@ -76,10 +77,10 @@ def create_styled_button( btn.pack(side="left", padx=10, pady=5, ipadx=5, ipady=2) # Explicitly typed event handlers for hover effects - def on_enter(event: Any) -> None: + def on_enter(event: typing.Any) -> None: btn.config(bg="#0056b3") - def on_leave(event: Any) -> None: + def on_leave(event: typing.Any) -> None: btn.config(bg="#007bff") btn.bind("", on_enter) @@ -140,10 +141,10 @@ def create_styled_button_grid( ) # Basic hover effect - def on_enter(event: Any) -> None: + def on_enter(event: typing.Any) -> None: btn.config(bg="#0056b3") - def on_leave(event: Any) -> None: + def on_leave(event: typing.Any) -> None: btn.config(bg="#007bff") btn.bind("", on_enter) @@ -272,7 +273,7 @@ def _initial_connect_wrapper(self) -> None: """Wrapper to run _initial_connect as an asyncio task.""" asyncio.create_task(self._initial_connect()) - def _schedule_dobot_task(self, task_coro: Awaitable[Any], description: str) -> None: + def _schedule_dobot_task(self, task_coro: typing.Awaitable[typing.Any], description: str) -> None: """ Helper to schedule an asynchronous Dobot task and update the GUI status. This method is called from the Tkinter thread, so it uses call_soon_threadsafe diff --git a/dobotWrapperPy/dobotSupliment.py b/dobotWrapperPy/dobotSupliment.py index 56ddce8..1f5d1d7 100644 --- a/dobotWrapperPy/dobotSupliment.py +++ b/dobotWrapperPy/dobotSupliment.py @@ -1,9 +1,9 @@ import math from typing import Tuple -from enum import Enum +import enum -class Direction(Enum): +class Direction(enum.Enum): POSITIVE = 1 NEGATIVE = -1 diff --git a/dobotWrapperPy/dobotapi.py b/dobotWrapperPy/dobotapi.py index be2474d..21954ad 100644 --- a/dobotWrapperPy/dobotapi.py +++ b/dobotWrapperPy/dobotapi.py @@ -82,7 +82,7 @@ def __init__( self.verbose = verbose self.lock = threading.Lock() self.conn = dobot_connection - is_open = self.conn.serial_conn.isOpen() + is_open = self.conn.serial_conn.is_open if self.verbose: print( "dobot: %s open" % self.conn.serial_conn.name @@ -106,9 +106,8 @@ def close(self) -> None: hasattr(self, "conn") and self.conn is not None and hasattr(self.conn, "serial_conn") - and self.conn.serial_conn - is not None # Ensure serial_conn itself is not None - and hasattr(self.conn.serial_conn, "name") + and self.conn.serial_conn is not None + and self.conn.serial_conn.name is not None ): port_name = self.conn.serial_conn.name print(f"dobot: {port_name} closed") @@ -261,7 +260,7 @@ def _read_message(self) -> Optional[Message]: """ time.sleep(0.05) # Allow data to arrive byte_buffer = self.conn.serial_conn.read_all() - if len(byte_buffer) > 0: + if byte_buffer is not None and len(byte_buffer) > 0: msg = Message(byte_buffer) if self.verbose: print("dobot: <<", msg) diff --git a/dobotWrapperPy/dobotasync.py b/dobotWrapperPy/dobotasync.py index 4381e0b..b77368a 100644 --- a/dobotWrapperPy/dobotasync.py +++ b/dobotWrapperPy/dobotasync.py @@ -35,13 +35,14 @@ tagARCCmd, ) import asyncio -from typing import Tuple, Optional, Set, Any, Callable, Awaitable, TypeVar -from enum import Enum +from typing import Tuple, Optional, Set, Callable, TypeVar +import typing +import enum import signal import sys -class EndEffectorType(Enum): +class EndEffectorType(enum.Enum): CUP = 0 GRIPPER = 1 LASER = 2 @@ -64,7 +65,7 @@ async def connect(self) -> None: self._loop = asyncio.get_running_loop() self.dobotApiInterface.initialize_robot() - def _on_sigint(self, signum: int, frame: Optional[Any]) -> None: + def _on_sigint(self, signum: int, frame: Optional[typing.Any]) -> None: print("SIGINT received. Force Stopping robot...") self.force_stop() match self._endEffectorType: @@ -84,7 +85,7 @@ def __del__(self) -> None: if hasattr(self, "dobotApiInterface") and self.dobotApiInterface is not None: del self.dobotApiInterface - def _run_in_loop(self, func: Callable[..., _T], *args: Any) -> Awaitable[_T]: + def _run_in_loop(self, func: Callable[..., _T], *args: typing.Any) -> typing.Awaitable[_T]: if self._loop is None: raise Exception("Dobot not connected") return self._loop.run_in_executor(None, func, *args) diff --git a/dobotWrapperPy/enums/CPMode.py b/dobotWrapperPy/enums/CPMode.py index c655938..d6eced8 100644 --- a/dobotWrapperPy/enums/CPMode.py +++ b/dobotWrapperPy/enums/CPMode.py @@ -1,6 +1,6 @@ -from enum import Enum +import enum -class CPMode(Enum): +class CPMode(enum.Enum): RELATIVE = 0 ABSOLUTE = 1 diff --git a/dobotWrapperPy/enums/CommunicationProtocolIDs.py b/dobotWrapperPy/enums/CommunicationProtocolIDs.py index 228bfa1..5416b83 100644 --- a/dobotWrapperPy/enums/CommunicationProtocolIDs.py +++ b/dobotWrapperPy/enums/CommunicationProtocolIDs.py @@ -1,7 +1,7 @@ -from enum import Enum +import enum -class CommunicationProtocolIDs(Enum): +class CommunicationProtocolIDs(enum.Enum): # INFO DEVICE_INFO_BASE = 0 DEVICE_SN = DEVICE_INFO_BASE + 0 diff --git a/dobotWrapperPy/enums/EMotorIndex.py b/dobotWrapperPy/enums/EMotorIndex.py index 7b7c3ed..b5db238 100644 --- a/dobotWrapperPy/enums/EMotorIndex.py +++ b/dobotWrapperPy/enums/EMotorIndex.py @@ -1,6 +1,6 @@ -from enum import Enum +import enum -class EMotorIndex(Enum): +class EMotorIndex(enum.Enum): STEPPER1 = 0 STEPPER2 = 1 diff --git a/dobotWrapperPy/enums/HHTTrigMode.py b/dobotWrapperPy/enums/HHTTrigMode.py index 9e513b0..7105647 100644 --- a/dobotWrapperPy/enums/HHTTrigMode.py +++ b/dobotWrapperPy/enums/HHTTrigMode.py @@ -1,6 +1,6 @@ -from enum import Enum +import enum -class HHTTrigMode(Enum): +class HHTTrigMode(enum.Enum): TriggeredOnKeyRelease = 0x0 TriggeredOnPeriodicInterval = 0x1 diff --git a/dobotWrapperPy/enums/IOFunction.py b/dobotWrapperPy/enums/IOFunction.py index 7176345..86f39f1 100644 --- a/dobotWrapperPy/enums/IOFunction.py +++ b/dobotWrapperPy/enums/IOFunction.py @@ -1,7 +1,7 @@ -from enum import Enum +import enum -class IOFunction(Enum): +class IOFunction(enum.Enum): DUMMY = 0 # Do not config DO = 1 # IO output PWM = 2 # PWM Output diff --git a/dobotWrapperPy/enums/alarm.py b/dobotWrapperPy/enums/alarm.py index 671b076..9283a51 100644 --- a/dobotWrapperPy/enums/alarm.py +++ b/dobotWrapperPy/enums/alarm.py @@ -1,8 +1,8 @@ -from enum import Enum import struct +import enum -class Alarm(Enum): +class Alarm(enum.Enum): COMMON_RESETTING = 0x00 COMMON_UNDEFINED_INSTRUCTION = 0x01 COMMON_FILE_SYSTEM = 0x02 diff --git a/dobotWrapperPy/enums/jogCmd.py b/dobotWrapperPy/enums/jogCmd.py index 82c68eb..e74ed2b 100644 --- a/dobotWrapperPy/enums/jogCmd.py +++ b/dobotWrapperPy/enums/jogCmd.py @@ -1,7 +1,7 @@ -from enum import Enum +import enum -class JogCmd(Enum): +class JogCmd(enum.Enum): IDEL = 0 # Void AP_DOWN = 1 # X+/Joint1+ AN_DOWN = 2 # X-/Joint1- diff --git a/dobotWrapperPy/enums/jogMode.py b/dobotWrapperPy/enums/jogMode.py index dd03c1c..c2b18d3 100644 --- a/dobotWrapperPy/enums/jogMode.py +++ b/dobotWrapperPy/enums/jogMode.py @@ -1,6 +1,6 @@ -from enum import Enum +import enum -class JogMode(Enum): +class JogMode(enum.Enum): COORDINATE = 0 JOINT = 1 diff --git a/dobotWrapperPy/enums/level.py b/dobotWrapperPy/enums/level.py index e6383b6..07f2d11 100644 --- a/dobotWrapperPy/enums/level.py +++ b/dobotWrapperPy/enums/level.py @@ -1,6 +1,6 @@ -from enum import Enum +import enum -class Level(Enum): +class Level(enum.Enum): LOW = 0 HIGH = 1 diff --git a/dobotWrapperPy/enums/ptpMode.py b/dobotWrapperPy/enums/ptpMode.py index eea1486..82658c3 100644 --- a/dobotWrapperPy/enums/ptpMode.py +++ b/dobotWrapperPy/enums/ptpMode.py @@ -1,7 +1,7 @@ -from enum import Enum +import enum -class PTPMode(Enum): +class PTPMode(enum.Enum): """ 1. JUMP_XYZ, Jump mode, diff --git a/dobotWrapperPy/enums/py.typed b/dobotWrapperPy/enums/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/dobotWrapperPy/enums/realTimeTrack.py b/dobotWrapperPy/enums/realTimeTrack.py index 1c2c64c..a05e7d7 100644 --- a/dobotWrapperPy/enums/realTimeTrack.py +++ b/dobotWrapperPy/enums/realTimeTrack.py @@ -1,6 +1,6 @@ -from enum import Enum +import enum -class RealTimeTrack(Enum): +class RealTimeTrack(enum.Enum): NONREALTIME = 0 REALTIME = 1 diff --git a/dobotWrapperPy/enums/tagVersionColorSensorAndIR.py b/dobotWrapperPy/enums/tagVersionColorSensorAndIR.py index 1bd7845..3fded52 100644 --- a/dobotWrapperPy/enums/tagVersionColorSensorAndIR.py +++ b/dobotWrapperPy/enums/tagVersionColorSensorAndIR.py @@ -1,6 +1,6 @@ -from enum import Enum +import enum -class TagVersionColorSensorAndIR(Enum): +class TagVersionColorSensorAndIR(enum.Enum): VERSION1 = 0 VERSION2 = 1 diff --git a/dobotWrapperPy/enums/tagVersionRail.py b/dobotWrapperPy/enums/tagVersionRail.py index 78a2971..9391286 100644 --- a/dobotWrapperPy/enums/tagVersionRail.py +++ b/dobotWrapperPy/enums/tagVersionRail.py @@ -1,6 +1,6 @@ -from enum import Enum +import enum -class tagVersionRail(Enum): +class tagVersionRail(enum.Enum): VER_V1 = 0 VER_V2 = 1 diff --git a/dobotWrapperPy/enums/triggerCondition.py b/dobotWrapperPy/enums/triggerCondition.py index b636237..64e6db1 100644 --- a/dobotWrapperPy/enums/triggerCondition.py +++ b/dobotWrapperPy/enums/triggerCondition.py @@ -1,7 +1,7 @@ -from enum import Enum +import enum -class TriggerCondition(Enum): +class TriggerCondition(enum.Enum): LEVEL_EQUAL = 0 AD_LESS = 0 LEVEL_UNEQUAL = 1 diff --git a/dobotWrapperPy/enums/triggerMode.py b/dobotWrapperPy/enums/triggerMode.py index 7c2d0b7..9512064 100644 --- a/dobotWrapperPy/enums/triggerMode.py +++ b/dobotWrapperPy/enums/triggerMode.py @@ -1,6 +1,6 @@ -from enum import Enum +import enum -class TriggerMode(Enum): +class TriggerMode(enum.Enum): LEVEL = 0 AD = 1 diff --git a/mypy.ini b/mypy.ini index b1efbe8..98c6797 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,2 +1,11 @@ [mypy] strict = True +python_version = 3.13 +ignore_errors = True + +[mypy-dobotWrapperPy] +namespace_packages = True +ignore_missing_imports = True + +[stubtest] +custom_typeshed_dir = stubs/ diff --git a/requirements.txt b/requirements.txt index 32e78a2..f12a639 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,31 @@ +attrs==25.3.0 +Automat==25.4.16 +CacheControl==0.14.3 +certifi==2025.4.26 +charset-normalizer==3.4.2 +ConfigArgParse==1.7 +constantly==23.10.4 +docutils==0.21.2 +filelock==3.18.0 +hyperlink==21.0.0 +idna==3.10 +incremental==24.7.2 iniconfig==2.1.0 +lunr==0.7.0.post1 +msgpack==1.1.0 +mypy==1.16.1 +mypy_extensions==1.1.0 packaging==25.0 +pathspec==0.12.1 +platformdirs==4.3.8 pluggy==1.6.0 +pydoctor==24.11.2 pyserial==3.5 pytest==8.3.5 +requests==2.32.3 setuptools==80.8.0 +Twisted==24.11.0 +types-pyserial==3.5.0.20250326 +typing_extensions==4.13.2 +urllib3==2.4.0 +zope.interface==7.2