makcu 0.1.4__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- makcu/__init__.py +71 -9
- makcu/__main__.py +254 -37
- makcu/conftest.py +27 -17
- makcu/connection.py +427 -231
- makcu/controller.py +348 -86
- makcu/makcu.pyi +13 -0
- makcu/mouse.py +219 -116
- makcu/py.typed +2 -0
- makcu/test_suite.py +112 -34
- makcu-0.2.0.dist-info/METADATA +1141 -0
- makcu-0.2.0.dist-info/RECORD +16 -0
- makcu-0.1.4.dist-info/METADATA +0 -274
- makcu-0.1.4.dist-info/RECORD +0 -14
- {makcu-0.1.4.dist-info → makcu-0.2.0.dist-info}/WHEEL +0 -0
- {makcu-0.1.4.dist-info → makcu-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {makcu-0.1.4.dist-info → makcu-0.2.0.dist-info}/top_level.txt +0 -0
makcu/controller.py
CHANGED
@@ -1,125 +1,315 @@
|
|
1
|
+
import asyncio
|
1
2
|
import random
|
2
3
|
import time
|
4
|
+
from typing import Optional, Dict, Callable, Union, List, Any
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
3
6
|
from .mouse import Mouse
|
4
7
|
from .connection import SerialTransport
|
5
8
|
from .errors import MakcuConnectionError
|
6
9
|
from .enums import MouseButton
|
7
10
|
|
8
11
|
class MakcuController:
|
9
|
-
|
10
|
-
|
12
|
+
"""Ultra-optimized MakcuController for gaming performance"""
|
13
|
+
|
14
|
+
# Pre-computed lock mappings for O(1) access
|
15
|
+
_BUTTON_LOCK_MAP = {
|
16
|
+
MouseButton.LEFT: 'lock_left',
|
17
|
+
MouseButton.RIGHT: 'lock_right',
|
18
|
+
MouseButton.MIDDLE: 'lock_middle',
|
19
|
+
MouseButton.MOUSE4: 'lock_side1',
|
20
|
+
MouseButton.MOUSE5: 'lock_side2',
|
21
|
+
}
|
22
|
+
|
23
|
+
def __init__(self, fallback_com_port: str = "", debug: bool = False,
|
24
|
+
send_init: bool = True, auto_reconnect: bool = True,
|
25
|
+
override_port: bool = False) -> None:
|
26
|
+
self.transport = SerialTransport(
|
27
|
+
fallback_com_port,
|
28
|
+
debug=debug,
|
29
|
+
send_init=send_init,
|
30
|
+
auto_reconnect=auto_reconnect,
|
31
|
+
override_port=override_port
|
32
|
+
)
|
11
33
|
self.mouse = Mouse(self.transport)
|
12
|
-
|
13
|
-
|
34
|
+
self._executor = ThreadPoolExecutor(max_workers=1)
|
35
|
+
self._connection_callbacks: List[Callable[[bool], None]] = []
|
36
|
+
|
37
|
+
# Cache connection state to avoid repeated checks
|
38
|
+
self._connected = False
|
39
|
+
|
40
|
+
# Connection management
|
41
|
+
def connect(self) -> None:
|
42
|
+
"""Connect with state caching"""
|
14
43
|
self.transport.connect()
|
44
|
+
self._connected = True
|
45
|
+
self._notify_connection_change(True)
|
15
46
|
|
16
|
-
def disconnect(self):
|
47
|
+
def disconnect(self) -> None:
|
48
|
+
"""Disconnect with cleanup"""
|
17
49
|
self.transport.disconnect()
|
50
|
+
self._connected = False
|
51
|
+
self._notify_connection_change(False)
|
52
|
+
self._executor.shutdown(wait=False)
|
53
|
+
|
54
|
+
def is_connected(self) -> bool:
|
55
|
+
"""Fast cached connection check"""
|
56
|
+
return self._connected and self.transport.is_connected()
|
18
57
|
|
19
|
-
def
|
20
|
-
|
58
|
+
def _check_connection(self) -> None:
|
59
|
+
"""Inline connection check for speed"""
|
60
|
+
if not self._connected:
|
61
|
+
raise MakcuConnectionError("Not connected")
|
21
62
|
|
22
|
-
def
|
23
|
-
|
63
|
+
def _notify_connection_change(self, connected: bool) -> None:
|
64
|
+
"""Notify callbacks with error suppression"""
|
65
|
+
for callback in self._connection_callbacks:
|
66
|
+
try:
|
67
|
+
callback(connected)
|
68
|
+
except Exception:
|
69
|
+
pass # Silently ignore for performance
|
70
|
+
|
71
|
+
# Optimized mouse operations
|
72
|
+
def click(self, button: MouseButton) -> None:
|
73
|
+
"""Optimized click - direct command"""
|
74
|
+
if not self._connected:
|
24
75
|
raise MakcuConnectionError("Not connected")
|
76
|
+
self.mouse.press(button)
|
77
|
+
self.mouse.release(button)
|
25
78
|
|
26
|
-
def
|
27
|
-
|
79
|
+
def double_click(self, button: MouseButton) -> None:
|
80
|
+
"""Double click with minimal delay"""
|
81
|
+
if not self._connected:
|
82
|
+
raise MakcuConnectionError("Not connected")
|
83
|
+
self.mouse.press(button)
|
84
|
+
self.mouse.release(button)
|
85
|
+
# 1ms delay for double click recognition
|
86
|
+
time.sleep(0.001)
|
28
87
|
self.mouse.press(button)
|
29
88
|
self.mouse.release(button)
|
30
89
|
|
31
|
-
def move(self, dx: int, dy: int):
|
32
|
-
|
90
|
+
def move(self, dx: int, dy: int) -> None:
|
91
|
+
"""Direct move"""
|
92
|
+
if not self._connected:
|
93
|
+
raise MakcuConnectionError("Not connected")
|
33
94
|
self.mouse.move(dx, dy)
|
34
95
|
|
35
|
-
def scroll(self, delta: int):
|
36
|
-
|
96
|
+
def scroll(self, delta: int) -> None:
|
97
|
+
"""Direct scroll"""
|
98
|
+
if not self._connected:
|
99
|
+
raise MakcuConnectionError("Not connected")
|
37
100
|
self.mouse.scroll(delta)
|
38
101
|
|
39
|
-
def
|
40
|
-
|
41
|
-
self.
|
102
|
+
def press(self, button: MouseButton) -> None:
|
103
|
+
"""Direct press"""
|
104
|
+
if not self._connected:
|
105
|
+
raise MakcuConnectionError("Not connected")
|
106
|
+
self.mouse.press(button)
|
42
107
|
|
43
|
-
def
|
44
|
-
|
45
|
-
self.
|
108
|
+
def release(self, button: MouseButton) -> None:
|
109
|
+
"""Direct release"""
|
110
|
+
if not self._connected:
|
111
|
+
raise MakcuConnectionError("Not connected")
|
112
|
+
self.mouse.release(button)
|
46
113
|
|
47
|
-
|
48
|
-
|
49
|
-
|
114
|
+
# Advanced movement - unchanged but with connection check optimization
|
115
|
+
def move_smooth(self, dx: int, dy: int, segments: int = 10) -> None:
|
116
|
+
"""Smooth movement"""
|
117
|
+
if not self._connected:
|
118
|
+
raise MakcuConnectionError("Not connected")
|
119
|
+
self.mouse.move_smooth(dx, dy, segments)
|
50
120
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
121
|
+
def move_bezier(self, dx: int, dy: int, segments: int = 20,
|
122
|
+
ctrl_x: Optional[int] = None, ctrl_y: Optional[int] = None) -> None:
|
123
|
+
"""Bezier curve movement"""
|
124
|
+
if not self._connected:
|
125
|
+
raise MakcuConnectionError("Not connected")
|
126
|
+
if ctrl_x is None:
|
127
|
+
ctrl_x = dx // 2
|
128
|
+
if ctrl_y is None:
|
129
|
+
ctrl_y = dy // 2
|
130
|
+
self.mouse.move_bezier(dx, dy, segments, ctrl_x, ctrl_y)
|
54
131
|
|
55
|
-
|
56
|
-
|
132
|
+
# Optimized lock API
|
133
|
+
def lock(self, target: Union[MouseButton, str]) -> None:
|
134
|
+
"""Lock with fast lookup"""
|
135
|
+
if not self._connected:
|
136
|
+
raise MakcuConnectionError("Not connected")
|
137
|
+
|
138
|
+
if isinstance(target, MouseButton):
|
139
|
+
if target in self._BUTTON_LOCK_MAP:
|
140
|
+
getattr(self.mouse, self._BUTTON_LOCK_MAP[target])(True)
|
141
|
+
else:
|
142
|
+
raise ValueError(f"Unsupported button: {target}")
|
143
|
+
elif target.upper() in ['X', 'Y']:
|
144
|
+
if target.upper() == 'X':
|
145
|
+
self.mouse.lock_x(True)
|
146
|
+
else:
|
147
|
+
self.mouse.lock_y(True)
|
148
|
+
else:
|
149
|
+
raise ValueError(f"Invalid lock target: {target}")
|
150
|
+
|
151
|
+
def unlock(self, target: Union[MouseButton, str]) -> None:
|
152
|
+
"""Unlock with fast lookup"""
|
153
|
+
if not self._connected:
|
154
|
+
raise MakcuConnectionError("Not connected")
|
155
|
+
|
156
|
+
if isinstance(target, MouseButton):
|
157
|
+
if target in self._BUTTON_LOCK_MAP:
|
158
|
+
getattr(self.mouse, self._BUTTON_LOCK_MAP[target])(False)
|
159
|
+
else:
|
160
|
+
raise ValueError(f"Unsupported button: {target}")
|
161
|
+
elif target.upper() in ['X', 'Y']:
|
162
|
+
if target.upper() == 'X':
|
163
|
+
self.mouse.lock_x(False)
|
164
|
+
else:
|
165
|
+
self.mouse.lock_y(False)
|
166
|
+
else:
|
167
|
+
raise ValueError(f"Invalid unlock target: {target}")
|
168
|
+
|
169
|
+
# Direct lock methods for backward compatibility
|
170
|
+
def lock_left(self, lock: bool) -> None:
|
171
|
+
if not self._connected:
|
172
|
+
raise MakcuConnectionError("Not connected")
|
57
173
|
self.mouse.lock_left(lock)
|
58
174
|
|
59
|
-
def lock_middle(self, lock: bool):
|
60
|
-
self.
|
175
|
+
def lock_middle(self, lock: bool) -> None:
|
176
|
+
if not self._connected:
|
177
|
+
raise MakcuConnectionError("Not connected")
|
61
178
|
self.mouse.lock_middle(lock)
|
62
179
|
|
63
|
-
def lock_right(self, lock: bool):
|
64
|
-
self.
|
180
|
+
def lock_right(self, lock: bool) -> None:
|
181
|
+
if not self._connected:
|
182
|
+
raise MakcuConnectionError("Not connected")
|
65
183
|
self.mouse.lock_right(lock)
|
66
184
|
|
67
|
-
def lock_side1(self, lock: bool):
|
68
|
-
self.
|
185
|
+
def lock_side1(self, lock: bool) -> None:
|
186
|
+
if not self._connected:
|
187
|
+
raise MakcuConnectionError("Not connected")
|
69
188
|
self.mouse.lock_side1(lock)
|
70
189
|
|
71
|
-
def lock_side2(self, lock: bool):
|
72
|
-
self.
|
190
|
+
def lock_side2(self, lock: bool) -> None:
|
191
|
+
if not self._connected:
|
192
|
+
raise MakcuConnectionError("Not connected")
|
73
193
|
self.mouse.lock_side2(lock)
|
74
194
|
|
75
|
-
def lock_x(self, lock: bool):
|
76
|
-
self.
|
195
|
+
def lock_x(self, lock: bool) -> None:
|
196
|
+
if not self._connected:
|
197
|
+
raise MakcuConnectionError("Not connected")
|
77
198
|
self.mouse.lock_x(lock)
|
78
199
|
|
79
|
-
def lock_y(self, lock: bool):
|
80
|
-
self.
|
200
|
+
def lock_y(self, lock: bool) -> None:
|
201
|
+
if not self._connected:
|
202
|
+
raise MakcuConnectionError("Not connected")
|
81
203
|
self.mouse.lock_y(lock)
|
82
204
|
|
83
|
-
def
|
84
|
-
|
205
|
+
def lock_mouse_x(self, lock: bool) -> None:
|
206
|
+
"""Alias for lock_x"""
|
207
|
+
self.lock_x(lock)
|
208
|
+
|
209
|
+
def lock_mouse_y(self, lock: bool) -> None:
|
210
|
+
"""Alias for lock_y"""
|
211
|
+
self.lock_y(lock)
|
212
|
+
|
213
|
+
def is_locked(self, button: MouseButton) -> bool:
|
214
|
+
"""Check lock state"""
|
215
|
+
if not self._connected:
|
216
|
+
raise MakcuConnectionError("Not connected")
|
217
|
+
return self.mouse.is_locked(button)
|
218
|
+
|
219
|
+
def get_all_lock_states(self) -> Dict[str, bool]:
|
220
|
+
"""Get all lock states"""
|
221
|
+
if not self._connected:
|
222
|
+
raise MakcuConnectionError("Not connected")
|
223
|
+
return self.mouse.get_all_lock_states()
|
224
|
+
|
225
|
+
# Device operations
|
226
|
+
def spoof_serial(self, serial: str) -> None:
|
227
|
+
"""Spoof device serial"""
|
228
|
+
if not self._connected:
|
229
|
+
raise MakcuConnectionError("Not connected")
|
85
230
|
self.mouse.spoof_serial(serial)
|
86
231
|
|
87
|
-
def reset_serial(self):
|
88
|
-
|
232
|
+
def reset_serial(self) -> None:
|
233
|
+
"""Reset device serial"""
|
234
|
+
if not self._connected:
|
235
|
+
raise MakcuConnectionError("Not connected")
|
89
236
|
self.mouse.reset_serial()
|
90
237
|
|
91
|
-
def get_device_info(self):
|
92
|
-
|
238
|
+
def get_device_info(self) -> Dict[str, str]:
|
239
|
+
"""Get device information"""
|
240
|
+
if not self._connected:
|
241
|
+
raise MakcuConnectionError("Not connected")
|
93
242
|
return self.mouse.get_device_info()
|
94
243
|
|
95
|
-
def get_firmware_version(self):
|
96
|
-
|
244
|
+
def get_firmware_version(self) -> str:
|
245
|
+
"""Get firmware version"""
|
246
|
+
if not self._connected:
|
247
|
+
raise MakcuConnectionError("Not connected")
|
97
248
|
return self.mouse.get_firmware_version()
|
98
249
|
|
250
|
+
# Button monitoring - direct transport access for speed
|
99
251
|
def get_button_mask(self) -> int:
|
100
|
-
|
252
|
+
"""Get current button mask"""
|
253
|
+
if not self._connected:
|
254
|
+
raise MakcuConnectionError("Not connected")
|
101
255
|
return self.transport.get_button_mask()
|
102
256
|
|
103
|
-
def
|
104
|
-
|
105
|
-
|
257
|
+
def get_button_states(self) -> Dict[str, bool]:
|
258
|
+
"""Get current button states"""
|
259
|
+
if not self._connected:
|
260
|
+
raise MakcuConnectionError("Not connected")
|
261
|
+
return self.transport.get_button_states()
|
262
|
+
|
263
|
+
def is_pressed(self, button: MouseButton) -> bool:
|
264
|
+
"""Check if button is pressed"""
|
265
|
+
if not self._connected:
|
266
|
+
raise MakcuConnectionError("Not connected")
|
267
|
+
return self.transport.get_button_states().get(button.name.lower(), False)
|
268
|
+
|
269
|
+
def enable_button_monitoring(self, enable: bool = True) -> None:
|
270
|
+
"""Enable/disable button monitoring"""
|
271
|
+
if not self._connected:
|
272
|
+
raise MakcuConnectionError("Not connected")
|
273
|
+
self.transport.enable_button_monitoring(enable)
|
274
|
+
|
275
|
+
def set_button_callback(self, callback: Optional[Callable[[MouseButton, bool], None]]) -> None:
|
276
|
+
"""Set button event callback"""
|
277
|
+
if not self._connected:
|
278
|
+
raise MakcuConnectionError("Not connected")
|
279
|
+
self.transport.set_button_callback(callback)
|
280
|
+
|
281
|
+
# Connection management
|
282
|
+
def on_connection_change(self, callback: Callable[[bool], None]) -> None:
|
283
|
+
"""Register connection status change callback"""
|
284
|
+
self._connection_callbacks.append(callback)
|
285
|
+
|
286
|
+
def remove_connection_callback(self, callback: Callable[[bool], None]) -> None:
|
287
|
+
"""Remove connection status callback"""
|
288
|
+
if callback in self._connection_callbacks:
|
289
|
+
self._connection_callbacks.remove(callback)
|
106
290
|
|
291
|
+
# Human-like interactions with optimized timing
|
107
292
|
def click_human_like(self, button: MouseButton, count: int = 1,
|
108
|
-
|
109
|
-
|
293
|
+
profile: str = "normal", jitter: int = 0) -> None:
|
294
|
+
"""Human-like clicking optimized for gaming"""
|
295
|
+
if not self._connected:
|
296
|
+
raise MakcuConnectionError("Not connected")
|
110
297
|
|
298
|
+
# Pre-computed timing profiles (in milliseconds)
|
111
299
|
timing_profiles = {
|
112
300
|
"normal": (60, 120, 100, 180),
|
113
301
|
"fast": (30, 60, 50, 100),
|
114
302
|
"slow": (100, 180, 150, 300),
|
303
|
+
"variable": (40, 200, 80, 250),
|
304
|
+
"gaming": (20, 40, 30, 60), # New gaming profile
|
115
305
|
}
|
116
306
|
|
117
307
|
if profile not in timing_profiles:
|
118
|
-
raise ValueError(f"Invalid profile: {profile}
|
308
|
+
raise ValueError(f"Invalid profile: {profile}")
|
119
309
|
|
120
310
|
min_down, max_down, min_wait, max_wait = timing_profiles[profile]
|
121
311
|
|
122
|
-
for
|
312
|
+
for i in range(count):
|
123
313
|
if jitter > 0:
|
124
314
|
dx = random.randint(-jitter, jitter)
|
125
315
|
dy = random.randint(-jitter, jitter)
|
@@ -128,32 +318,104 @@ class MakcuController:
|
|
128
318
|
self.mouse.press(button)
|
129
319
|
time.sleep(random.uniform(min_down, max_down) / 1000.0)
|
130
320
|
self.mouse.release(button)
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
self.
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
self.
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
self.
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
self.
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
321
|
+
|
322
|
+
if i < count - 1:
|
323
|
+
time.sleep(random.uniform(min_wait, max_wait) / 1000.0)
|
324
|
+
|
325
|
+
def drag(self, start_x: int, start_y: int, end_x: int, end_y: int,
|
326
|
+
button: MouseButton = MouseButton.LEFT, duration: float = 1.0) -> None:
|
327
|
+
"""Optimized drag operation"""
|
328
|
+
if not self._connected:
|
329
|
+
raise MakcuConnectionError("Not connected")
|
330
|
+
|
331
|
+
# Move to start
|
332
|
+
self.move(start_x, start_y)
|
333
|
+
time.sleep(0.02) # Reduced from 0.05
|
334
|
+
|
335
|
+
# Press button
|
336
|
+
self.press(button)
|
337
|
+
time.sleep(0.02) # Reduced from 0.05
|
338
|
+
|
339
|
+
# Move to end with smooth motion
|
340
|
+
segments = max(10, int(duration * 30))
|
341
|
+
self.move_smooth(end_x - start_x, end_y - start_y, segments)
|
342
|
+
|
343
|
+
# Release button
|
344
|
+
time.sleep(0.02) # Reduced from 0.05
|
345
|
+
self.release(button)
|
346
|
+
|
347
|
+
# Context manager support
|
348
|
+
def __enter__(self):
|
349
|
+
"""Context manager entry"""
|
350
|
+
if not self.is_connected():
|
351
|
+
self.connect()
|
352
|
+
return self
|
353
|
+
|
354
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
355
|
+
"""Context manager exit"""
|
356
|
+
self.disconnect()
|
357
|
+
|
358
|
+
# Async methods for forward compatibility
|
359
|
+
async def async_connect(self) -> None:
|
360
|
+
"""Async connect"""
|
361
|
+
loop = asyncio.get_running_loop()
|
362
|
+
await loop.run_in_executor(self._executor, self.connect)
|
363
|
+
|
364
|
+
async def async_disconnect(self) -> None:
|
365
|
+
"""Async disconnect"""
|
366
|
+
loop = asyncio.get_running_loop()
|
367
|
+
await loop.run_in_executor(self._executor, self.disconnect)
|
368
|
+
|
369
|
+
async def async_click(self, button: MouseButton) -> None:
|
370
|
+
"""Async click"""
|
371
|
+
loop = asyncio.get_running_loop()
|
372
|
+
await loop.run_in_executor(self._executor, self.click, button)
|
373
|
+
|
374
|
+
async def async_move(self, dx: int, dy: int) -> None:
|
375
|
+
"""Async move"""
|
376
|
+
loop = asyncio.get_running_loop()
|
377
|
+
await loop.run_in_executor(self._executor, self.move, dx, dy)
|
378
|
+
|
379
|
+
async def async_scroll(self, delta: int) -> None:
|
380
|
+
"""Async scroll"""
|
381
|
+
loop = asyncio.get_running_loop()
|
382
|
+
await loop.run_in_executor(self._executor, self.scroll, delta)
|
383
|
+
|
384
|
+
# Async context manager
|
385
|
+
async def __aenter__(self):
|
386
|
+
"""Async context manager entry"""
|
387
|
+
await self.async_connect()
|
388
|
+
return self
|
389
|
+
|
390
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
391
|
+
"""Async context manager exit"""
|
392
|
+
await self.async_disconnect()
|
393
|
+
|
394
|
+
|
395
|
+
# Factory functions with connection optimization
|
396
|
+
def create_controller(fallback_com_port: str = "", debug: bool = False,
|
397
|
+
send_init: bool = True, auto_reconnect: bool = True) -> MakcuController:
|
398
|
+
"""Create and connect a controller"""
|
399
|
+
makcu = MakcuController(
|
400
|
+
fallback_com_port,
|
401
|
+
debug=debug,
|
402
|
+
send_init=send_init,
|
403
|
+
auto_reconnect=auto_reconnect
|
404
|
+
)
|
405
|
+
makcu.connect()
|
406
|
+
return makcu
|
407
|
+
|
408
|
+
|
409
|
+
async def create_async_controller(fallback_com_port: str = "", debug: bool = False,
|
410
|
+
send_init: bool = True, auto_reconnect: bool = True,
|
411
|
+
override_port: bool = False) -> MakcuController:
|
412
|
+
"""Create and connect a controller asynchronously"""
|
413
|
+
makcu = MakcuController(
|
414
|
+
fallback_com_port,
|
415
|
+
debug=debug,
|
416
|
+
send_init=send_init,
|
417
|
+
auto_reconnect=auto_reconnect,
|
418
|
+
override_port=override_port
|
419
|
+
)
|
420
|
+
await makcu.async_connect()
|
421
|
+
return makcu
|
makcu/makcu.pyi
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
from typing import Optional, Dict, Callable, List, Union
|
2
|
+
from .controller import MakcuController
|
3
|
+
from .enums import MouseButton
|
4
|
+
from .errors import MakcuError, MakcuConnectionError, MakcuCommandError, MakcuTimeoutError, MakcuResponseError
|
5
|
+
|
6
|
+
__version__: str
|
7
|
+
__all__: List[str]
|
8
|
+
|
9
|
+
def create_controller(
|
10
|
+
fallback_com_port: str = "",
|
11
|
+
debug: bool = False,
|
12
|
+
send_init: bool = True
|
13
|
+
) -> MakcuController: ...
|