GHIElectronics.Endpoint.Devices.UsbHost 0.5.3__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.
- GHIElectronics/Endpoint/Devices/UsbHost/__init__.py +143 -0
- GHIElectronics/Endpoint/Devices/UsbHost/_input.py +410 -0
- GHIElectronics/Endpoint/Devices/UsbHost/_types.py +217 -0
- ghielectronics_endpoint_devices_usbhost-0.5.3.dist-info/METADATA +20 -0
- ghielectronics_endpoint_devices_usbhost-0.5.3.dist-info/RECORD +7 -0
- ghielectronics_endpoint_devices_usbhost-0.5.3.dist-info/WHEEL +5 -0
- ghielectronics_endpoint_devices_usbhost-0.5.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""USB devices of Endpoint, with the same API as .NET's
|
|
2
|
+
GHIElectronics.Endpoint.Devices.UsbHost:
|
|
3
|
+
|
|
4
|
+
import serial
|
|
5
|
+
from GHIElectronics.Endpoint.Core import DeviceConnectionStatus
|
|
6
|
+
from GHIElectronics.Endpoint.Devices.UsbHost import DeviceType, UsbHostController
|
|
7
|
+
|
|
8
|
+
usb = UsbHostController.GetDefault()
|
|
9
|
+
|
|
10
|
+
def changed(sender, e):
|
|
11
|
+
if e.DeviceStatus == DeviceConnectionStatus.Connected and e.Type == DeviceType.Serial:
|
|
12
|
+
port = serial.Serial(e.DeviceName, 115200) # "/dev/ttyUSB0"
|
|
13
|
+
|
|
14
|
+
usb.ConnectionChanged += changed
|
|
15
|
+
usb.Enable() # the devices already plugged in are reported too
|
|
16
|
+
|
|
17
|
+
One event for each thing a device gives: a keyboard with a touchpad gives
|
|
18
|
+
Keyboard and Mouse, a webcam gives Webcam and Audio (its microphone).
|
|
19
|
+
DeviceName is what to open it with (see DeviceType). The events come on the
|
|
20
|
+
library's USB thread. USB drives come as MassStorage: use StorageController
|
|
21
|
+
(GHIElectronics.Endpoint.Devices.Storage) for them.
|
|
22
|
+
|
|
23
|
+
Keyboards, mice and joysticks have TinyCLR's classes:
|
|
24
|
+
|
|
25
|
+
if e.DeviceStatus == DeviceConnectionStatus.Connected and e.Type == DeviceType.Mouse:
|
|
26
|
+
mouse = Mouse(e.DeviceName)
|
|
27
|
+
mouse.SetCursorBounds(Position(0, 0), Position(799, 479))
|
|
28
|
+
mouse.CursorMoved += lambda sender, a: print("Cursor at", a.NewPosition.X, a.NewPosition.Y)
|
|
29
|
+
mouse.ButtonChanged += lambda sender, a: print(a.Which.name, a.State.name)
|
|
30
|
+
|
|
31
|
+
A program's own devices: pyusb for one with no driver (DeviceType.Unknown),
|
|
32
|
+
the hid package (hidapi) for HID devices; the board has their libraries:
|
|
33
|
+
|
|
34
|
+
pip install pyusb hid
|
|
35
|
+
device = usb.core.find(idVendor=e.VendorId, idProduct=e.ProductId)
|
|
36
|
+
device = hid.Device(vid=e.VendorId, pid=e.ProductId)
|
|
37
|
+
|
|
38
|
+
A controller or device stays alive until it is disposed, also when the
|
|
39
|
+
program keeps no variable for it.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from GHIElectronics.Endpoint.Core import DeviceConnectionStatus
|
|
43
|
+
from GHIElectronics.Endpoint.Core._event import Event
|
|
44
|
+
from GHIElectronics.Endpoint.Core._native import UsbHostCallback, native
|
|
45
|
+
|
|
46
|
+
from ._input import BaseDevice, Joystick, Keyboard, Mouse, Printer, PrinterStatus
|
|
47
|
+
from ._types import ButtonState, Buttons, Cursor, DeviceType, HatSwitchDirection, Key, KeyState, Position
|
|
48
|
+
|
|
49
|
+
__all__ = ["BaseDevice", "ButtonState", "Buttons", "Cursor", "DeviceConnectionStatus", "DeviceType",
|
|
50
|
+
"HatSwitchDirection", "Joystick", "Key", "KeyState", "Keyboard", "Mouse", "Position", "Printer",
|
|
51
|
+
"PrinterStatus",
|
|
52
|
+
"UsbDeviceConnectionEventArgs", "UsbHostController"]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class UsbDeviceConnectionEventArgs:
|
|
56
|
+
def __init__(self, deviceStatus, id, type, vendorId, productId, deviceName, manufacturer, product, serialNumber):
|
|
57
|
+
self.DeviceStatus = deviceStatus
|
|
58
|
+
self.Id = id
|
|
59
|
+
"""The device's number: the same for all its events, a new one when it is plugged in again"""
|
|
60
|
+
self.Type = type
|
|
61
|
+
self.VendorId = vendorId
|
|
62
|
+
self.ProductId = productId
|
|
63
|
+
self.DeviceName = deviceName
|
|
64
|
+
"""What to open: "/dev/ttyUSB0", "eth1"... (see DeviceType)"""
|
|
65
|
+
self.Manufacturer = manufacturer
|
|
66
|
+
"""The device's own names ("Logitech", "USB Receiver"), empty if it has none"""
|
|
67
|
+
self.Product = product
|
|
68
|
+
self.SerialNumber = serialNumber
|
|
69
|
+
|
|
70
|
+
def __repr__(self):
|
|
71
|
+
return (f"UsbDeviceConnectionEventArgs({self.DeviceStatus.name}, Id={self.Id}, Type={self.Type.name}, "
|
|
72
|
+
f"DeviceName={self.DeviceName!r}, VendorId=0x{self.VendorId:04X}, ProductId=0x{self.ProductId:04X}, "
|
|
73
|
+
f"Manufacturer={self.Manufacturer!r}, Product={self.Product!r})")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _text(value):
|
|
77
|
+
return value.decode(errors="replace") if value else ""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _type(value):
|
|
81
|
+
try:
|
|
82
|
+
return DeviceType(value)
|
|
83
|
+
except ValueError:
|
|
84
|
+
return value # a type a newer libendpoint knows
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class UsbHostController:
|
|
88
|
+
@staticmethod
|
|
89
|
+
def GetDefault():
|
|
90
|
+
return UsbHostController()
|
|
91
|
+
|
|
92
|
+
def __init__(self):
|
|
93
|
+
self._event = Event()
|
|
94
|
+
self._handle = None
|
|
95
|
+
self._callback = UsbHostCallback(self._changed) # kept: the library calls it
|
|
96
|
+
handle = native.out_handle()
|
|
97
|
+
native.usbhost_create(self._callback, None, native.ref(handle))
|
|
98
|
+
self._handle = handle
|
|
99
|
+
native.keep_alive(self)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def ConnectionChanged(self):
|
|
103
|
+
"""A device connected or disconnected: handler(sender, e). Add with +=, remove with -="""
|
|
104
|
+
return self._event
|
|
105
|
+
|
|
106
|
+
@ConnectionChanged.setter
|
|
107
|
+
def ConnectionChanged(self, value):
|
|
108
|
+
if value is not self._event:
|
|
109
|
+
raise TypeError("Use += and -= to add and remove an event handler")
|
|
110
|
+
|
|
111
|
+
def _h(self):
|
|
112
|
+
if self._handle is None:
|
|
113
|
+
raise native.disposed("UsbHostController")
|
|
114
|
+
return self._handle
|
|
115
|
+
|
|
116
|
+
def Enable(self):
|
|
117
|
+
"""Starts reporting devices: first the ones already plugged in"""
|
|
118
|
+
native.usbhost_enable(self._h())
|
|
119
|
+
|
|
120
|
+
def Disable(self):
|
|
121
|
+
native.usbhost_disable(self._h())
|
|
122
|
+
|
|
123
|
+
def Dispose(self):
|
|
124
|
+
"""Stops the events (one being delivered finishes first)"""
|
|
125
|
+
if self._handle is not None:
|
|
126
|
+
native.usbhost_dispose(self._handle)
|
|
127
|
+
self._handle = None
|
|
128
|
+
native.let_go(self)
|
|
129
|
+
|
|
130
|
+
def _changed(self, context, status, id, type, vendorId, productId, deviceName, manufacturer, product,
|
|
131
|
+
serialNumber):
|
|
132
|
+
self._event(self, UsbDeviceConnectionEventArgs(DeviceConnectionStatus(status), id, _type(type), vendorId,
|
|
133
|
+
productId, _text(deviceName), _text(manufacturer),
|
|
134
|
+
_text(product), _text(serialNumber)))
|
|
135
|
+
|
|
136
|
+
def __enter__(self):
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
def __exit__(self, *exception):
|
|
140
|
+
self.Dispose()
|
|
141
|
+
|
|
142
|
+
def __del__(self):
|
|
143
|
+
self.Dispose()
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
"""USB keyboards, mice and joysticks with TinyCLR's API, and printers. Each is
|
|
2
|
+
opened with the DeviceName its UsbHostController event gives
|
|
3
|
+
("/dev/input/event2", "/dev/usb/lp0"); its events come on the device's own
|
|
4
|
+
thread; it stays alive until it is disposed."""
|
|
5
|
+
|
|
6
|
+
from enum import IntFlag
|
|
7
|
+
import ctypes
|
|
8
|
+
|
|
9
|
+
from GHIElectronics.Endpoint.Core._event import Event
|
|
10
|
+
from GHIElectronics.Endpoint.Core._native import InputCallback, UnpluggedCallback, native
|
|
11
|
+
|
|
12
|
+
from ._types import ButtonState, Buttons, Cursor, DeviceType, HatSwitchDirection, Key, KeyState, Position
|
|
13
|
+
|
|
14
|
+
# The libendpoint events (see Input::Event in libendpoint)
|
|
15
|
+
_KEY, _MOUSE_BUTTON, _MOUSE_MOVE, _WHEEL, _JOY_BUTTON, _JOY_MOVE, _HAT, _DISCONNECTED = range(1, 9)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _EventProperty:
|
|
19
|
+
"""An event: handlers are added with += and removed with -= (handler(sender, e))"""
|
|
20
|
+
|
|
21
|
+
def __set_name__(self, owner, name):
|
|
22
|
+
self.attribute = "_event_" + name
|
|
23
|
+
|
|
24
|
+
def __get__(self, target, owner=None):
|
|
25
|
+
return self if target is None else target.__dict__[self.attribute]
|
|
26
|
+
|
|
27
|
+
def __set__(self, target, value):
|
|
28
|
+
if value is not target.__dict__.get(self.attribute):
|
|
29
|
+
raise TypeError("Use += and -= to add and remove an event handler")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _EventArgs:
|
|
33
|
+
def __init__(self, **values):
|
|
34
|
+
self.__dict__.update(values)
|
|
35
|
+
|
|
36
|
+
def __repr__(self):
|
|
37
|
+
values = ", ".join(f"{name}={value!r}" for name, value in self.__dict__.items())
|
|
38
|
+
return f"{type(self).__name__}({values})"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _key(value):
|
|
42
|
+
try:
|
|
43
|
+
return Key(value)
|
|
44
|
+
except ValueError:
|
|
45
|
+
return value # a key TinyCLR has no name for: its USB key code
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BaseDevice:
|
|
49
|
+
"""A USB device the program uses: Keyboard, Mouse, Joystick or Printer"""
|
|
50
|
+
|
|
51
|
+
Disconnected = _EventProperty()
|
|
52
|
+
"""The device was unplugged: handler(sender, e)"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, deviceName, deviceType):
|
|
55
|
+
# The events exist before the device can raise them
|
|
56
|
+
for klass in type(self).__mro__:
|
|
57
|
+
for value in vars(klass).values():
|
|
58
|
+
if isinstance(value, _EventProperty):
|
|
59
|
+
self.__dict__[value.attribute] = Event()
|
|
60
|
+
self._handle = None
|
|
61
|
+
self._printer = deviceType == DeviceType.Printer
|
|
62
|
+
handle = native.out_handle()
|
|
63
|
+
vendor, product, connected, capabilities = (native.out_int() for _ in range(4))
|
|
64
|
+
# The callbacks are kept: the library calls them
|
|
65
|
+
if self._printer:
|
|
66
|
+
self._callback = UnpluggedCallback(lambda context: self.Disconnected(self, None))
|
|
67
|
+
native.printer_open(str(deviceName).encode(), self._callback, None, native.ref(handle))
|
|
68
|
+
native.printer_info(handle, native.ref(vendor), native.ref(product), native.ref(connected))
|
|
69
|
+
else:
|
|
70
|
+
self._callback = InputCallback(self._changed)
|
|
71
|
+
native.input_open(str(deviceName).encode(), int(deviceType), self._callback, None, native.ref(handle))
|
|
72
|
+
native.input_info(handle, native.ref(vendor), native.ref(product), native.ref(connected),
|
|
73
|
+
native.ref(capabilities))
|
|
74
|
+
self._handle = handle
|
|
75
|
+
native.keep_alive(self)
|
|
76
|
+
self._deviceName = str(deviceName)
|
|
77
|
+
self._type = deviceType
|
|
78
|
+
self._vendorId = vendor.value
|
|
79
|
+
self._productId = product.value
|
|
80
|
+
self._capabilities = capabilities.value
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def DeviceName(self):
|
|
84
|
+
"""What the device was opened with: "/dev/input/event2", "/dev/usb/lp0" """
|
|
85
|
+
return self._deviceName
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def Type(self):
|
|
89
|
+
return self._type
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def VendorId(self):
|
|
93
|
+
return self._vendorId
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def ProductId(self):
|
|
97
|
+
return self._productId
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def Connected(self):
|
|
101
|
+
if self._handle is None:
|
|
102
|
+
return False
|
|
103
|
+
values = [native.out_int() for _ in range(4)]
|
|
104
|
+
if self._printer:
|
|
105
|
+
native.printer_info(self._handle, *[native.ref(v) for v in values[:3]])
|
|
106
|
+
else:
|
|
107
|
+
native.input_info(self._handle, *[native.ref(v) for v in values])
|
|
108
|
+
return values[2].value != 0
|
|
109
|
+
|
|
110
|
+
def Dispose(self):
|
|
111
|
+
"""Stops the device's events (one being delivered finishes first)"""
|
|
112
|
+
if self._handle is not None:
|
|
113
|
+
(native.printer_close if self._printer else native.input_close)(self._handle)
|
|
114
|
+
self._handle = None
|
|
115
|
+
native.let_go(self)
|
|
116
|
+
|
|
117
|
+
def _h(self):
|
|
118
|
+
if self._handle is None:
|
|
119
|
+
raise native.disposed(type(self).__name__)
|
|
120
|
+
return self._handle
|
|
121
|
+
|
|
122
|
+
def _changed(self, context, event, a, b, c, d, e):
|
|
123
|
+
if event == _DISCONNECTED:
|
|
124
|
+
self.Disconnected(self, None)
|
|
125
|
+
else:
|
|
126
|
+
self._on_event(event, a, b, c, d, e)
|
|
127
|
+
|
|
128
|
+
def _on_event(self, event, a, b, c, d, e):
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
def __enter__(self):
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
def __exit__(self, *exception):
|
|
135
|
+
self.Dispose()
|
|
136
|
+
|
|
137
|
+
def __repr__(self):
|
|
138
|
+
return f"{type(self).__name__}({self._deviceName!r})"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Mouse(BaseDevice):
|
|
142
|
+
"""A USB mouse. The cursor stays between 0 and 512 at first, as on TinyCLR:
|
|
143
|
+
set it to the screen with SetCursorBounds.
|
|
144
|
+
|
|
145
|
+
mouse = Mouse(e.DeviceName) # from UsbHostController, DeviceType.Mouse
|
|
146
|
+
mouse.SetCursorBounds(Position(0, 0), Position(799, 479))
|
|
147
|
+
mouse.CursorMoved += lambda sender, a: print("Cursor at", a.NewPosition.X, a.NewPosition.Y)
|
|
148
|
+
mouse.ButtonChanged += lambda sender, a: print(a.Which.name, a.State.name)
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
ButtonChanged = _EventProperty()
|
|
152
|
+
"""A button was pressed or released: handler(sender, e) with e.Which (Buttons), e.State (ButtonState)"""
|
|
153
|
+
WheelMoved = _EventProperty()
|
|
154
|
+
"""The wheel moved: e.NewPosition, e.Delta"""
|
|
155
|
+
CursorMoved = _EventProperty()
|
|
156
|
+
"""The cursor moved: e.NewPosition, e.Delta (Position)"""
|
|
157
|
+
|
|
158
|
+
class ButtonChangedEventArgs(_EventArgs):
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
class WheelMovedEventArgs(_EventArgs):
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
class CursorMovedEventArgs(_EventArgs):
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
def __init__(self, deviceName):
|
|
168
|
+
super().__init__(deviceName, DeviceType.Mouse)
|
|
169
|
+
|
|
170
|
+
def _state(self):
|
|
171
|
+
values = [native.out_int() for _ in range(4)]
|
|
172
|
+
native.input_mouse_state(self._h(), *[native.ref(v) for v in values])
|
|
173
|
+
return [v.value for v in values]
|
|
174
|
+
|
|
175
|
+
def _button(self, button):
|
|
176
|
+
return ButtonState.Pressed if self._state()[3] & button else ButtonState.Released
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def LeftButtonState(self):
|
|
180
|
+
return self._button(Buttons.Left)
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def RightButtonState(self):
|
|
184
|
+
return self._button(Buttons.Right)
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def MiddleButtonState(self):
|
|
188
|
+
return self._button(Buttons.Middle)
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def Extended1ButtonState(self):
|
|
192
|
+
return self._button(Buttons.Extended1)
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def Extended2ButtonState(self):
|
|
196
|
+
return self._button(Buttons.Extended2)
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def CursorPosition(self):
|
|
200
|
+
x, y, _, _ = self._state()
|
|
201
|
+
return Position(x, y)
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def WheelPosition(self):
|
|
205
|
+
return self._state()[2]
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def AbsolutePosition(self):
|
|
209
|
+
"""False: a USB mouse moves relative"""
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
def SetScale(self, scale):
|
|
213
|
+
"""Scales the x, y, and wheel delta values"""
|
|
214
|
+
native.input_set_scale(self._h(), float(scale))
|
|
215
|
+
|
|
216
|
+
def SetCursorBounds(self, minimum, maximum):
|
|
217
|
+
"""The cursor's smallest and largest Position"""
|
|
218
|
+
native.input_set_cursor_bounds(self._h(), 0, minimum.X, maximum.X, minimum.Y, maximum.Y)
|
|
219
|
+
|
|
220
|
+
def SetCursorPosition(self, newPosition):
|
|
221
|
+
native.input_set_cursor_position(self._h(), newPosition.X, newPosition.Y)
|
|
222
|
+
|
|
223
|
+
def _on_event(self, event, a, b, c, d, e):
|
|
224
|
+
if event == _MOUSE_BUTTON:
|
|
225
|
+
self.ButtonChanged(self, Mouse.ButtonChangedEventArgs(Which=Buttons(a), State=ButtonState(b)))
|
|
226
|
+
elif event == _MOUSE_MOVE:
|
|
227
|
+
self.CursorMoved(self, Mouse.CursorMovedEventArgs(NewPosition=Position(a, b), Delta=Position(c, d)))
|
|
228
|
+
elif event == _WHEEL:
|
|
229
|
+
self.WheelMoved(self, Mouse.WheelMovedEventArgs(NewPosition=a, Delta=b))
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class Keyboard(BaseDevice):
|
|
233
|
+
"""A USB keyboard. CharDown and CharUp come only for keys with an ASCII
|
|
234
|
+
character (a US keyboard; Ctrl+A to Ctrl+Z give 1 to 26). A key held down
|
|
235
|
+
gives one KeyDown.
|
|
236
|
+
|
|
237
|
+
keyboard = Keyboard(e.DeviceName) # from UsbHostController, DeviceType.Keyboard
|
|
238
|
+
keyboard.CharDown += lambda sender, a: print(a.Ascii, end="", flush=True)
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
KeyUp = _EventProperty()
|
|
242
|
+
"""A key was released: handler(sender, e) with e.Which (Key) and e.Ascii (a str, "\\0" if none)"""
|
|
243
|
+
KeyDown = _EventProperty()
|
|
244
|
+
"""A key was pressed"""
|
|
245
|
+
CharUp = _EventProperty()
|
|
246
|
+
"""A key with an ASCII character was released"""
|
|
247
|
+
CharDown = _EventProperty()
|
|
248
|
+
"""A key with an ASCII character was pressed"""
|
|
249
|
+
|
|
250
|
+
class KeyboardEventArgs(_EventArgs):
|
|
251
|
+
pass
|
|
252
|
+
|
|
253
|
+
def __init__(self, deviceName):
|
|
254
|
+
super().__init__(deviceName, DeviceType.Keyboard)
|
|
255
|
+
|
|
256
|
+
def IsKeyPressed(self, key):
|
|
257
|
+
"""The KeyState of a key"""
|
|
258
|
+
state = native.out_int()
|
|
259
|
+
native.input_key_state(self._h(), int(key), native.ref(state))
|
|
260
|
+
return KeyState(state.value)
|
|
261
|
+
|
|
262
|
+
def _on_event(self, event, a, b, c, d, e):
|
|
263
|
+
if event != _KEY:
|
|
264
|
+
return
|
|
265
|
+
args = Keyboard.KeyboardEventArgs(Which=_key(a), Ascii=chr(c))
|
|
266
|
+
if b == KeyState.Pressed:
|
|
267
|
+
self.KeyDown(self, args)
|
|
268
|
+
if c:
|
|
269
|
+
self.CharDown(self, args)
|
|
270
|
+
else:
|
|
271
|
+
self.KeyUp(self, args)
|
|
272
|
+
if c:
|
|
273
|
+
self.CharUp(self, args)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class Joystick(BaseDevice):
|
|
277
|
+
"""A USB joystick or game pad. The sticks go from -512 to 512 at first, as on
|
|
278
|
+
TinyCLR: Cursor1 is the left stick, Cursor2 the right one. Buttons are
|
|
279
|
+
numbered from 0.
|
|
280
|
+
|
|
281
|
+
joystick = Joystick(e.DeviceName) # from UsbHostController, DeviceType.Joystick
|
|
282
|
+
joystick.CursorMoved += lambda sender, a: print(a.Which.name, a.NewPosition)
|
|
283
|
+
joystick.ButtonChanged += lambda sender, a: print("Button", a.Which, a.State.name)
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
ButtonChanged = _EventProperty()
|
|
287
|
+
"""A button was pressed or released: handler(sender, e) with e.Which (its number), e.State"""
|
|
288
|
+
CursorMoved = _EventProperty()
|
|
289
|
+
"""A stick moved: e.Which (Cursor), e.NewPosition, e.Delta"""
|
|
290
|
+
HatSwitchPressed = _EventProperty()
|
|
291
|
+
"""The hat switch moved: e.Direction (HatSwitchDirection)"""
|
|
292
|
+
|
|
293
|
+
class DeviceCapabilities(IntFlag):
|
|
294
|
+
CursorX = 0x01
|
|
295
|
+
CursorY = 0x02
|
|
296
|
+
Cursor2X = 0x04
|
|
297
|
+
Cursor2Y = 0x08
|
|
298
|
+
HatSwitch = 0x10
|
|
299
|
+
Buttons = 0x20
|
|
300
|
+
|
|
301
|
+
class ButtonChangedEventArgs(_EventArgs):
|
|
302
|
+
pass
|
|
303
|
+
|
|
304
|
+
class CursorMovedEventArgs(_EventArgs):
|
|
305
|
+
pass
|
|
306
|
+
|
|
307
|
+
class HatSwitchPressedEventArgs(_EventArgs):
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
def __init__(self, deviceName):
|
|
311
|
+
super().__init__(deviceName, DeviceType.Joystick)
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def Capabilities(self):
|
|
315
|
+
return Joystick.DeviceCapabilities(self._capabilities)
|
|
316
|
+
|
|
317
|
+
def _state(self, cursor):
|
|
318
|
+
x, y, hat = native.out_int(), native.out_int(), native.out_int()
|
|
319
|
+
buttons = native.out_uint32()
|
|
320
|
+
native.input_joystick_state(self._h(), int(cursor), native.ref(x), native.ref(y), native.ref(hat),
|
|
321
|
+
native.ref(buttons))
|
|
322
|
+
return Position(x.value, y.value), HatSwitchDirection(hat.value), buttons.value
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def CursorPosition(self):
|
|
326
|
+
return self._state(Cursor.Cursor1)[0]
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
def Cursor2Position(self):
|
|
330
|
+
return self._state(Cursor.Cursor2)[0]
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def CurrentHatSwitchDirection(self):
|
|
334
|
+
return self._state(Cursor.Cursor1)[1]
|
|
335
|
+
|
|
336
|
+
def SetCursorBounds(self, cursor, minX, maxX, minY, maxY):
|
|
337
|
+
native.input_set_cursor_bounds(self._h(), int(cursor), minX, maxX, minY, maxY)
|
|
338
|
+
|
|
339
|
+
def GetButtonState(self, buttonNumber):
|
|
340
|
+
"""The ButtonState of a button (0 to 31)"""
|
|
341
|
+
if not 0 <= buttonNumber <= 31:
|
|
342
|
+
from GHIElectronics.Endpoint.Core import ArgumentOutOfRangeException
|
|
343
|
+
raise ArgumentOutOfRangeException("The button number must be 0 to 31")
|
|
344
|
+
return ButtonState.Pressed if self._state(Cursor.Cursor1)[2] & (1 << buttonNumber) else ButtonState.Released
|
|
345
|
+
|
|
346
|
+
def _on_event(self, event, a, b, c, d, e):
|
|
347
|
+
if event == _JOY_BUTTON:
|
|
348
|
+
self.ButtonChanged(self, Joystick.ButtonChangedEventArgs(Which=a, State=ButtonState(b)))
|
|
349
|
+
elif event == _JOY_MOVE:
|
|
350
|
+
self.CursorMoved(self, Joystick.CursorMovedEventArgs(Which=Cursor(a), NewPosition=Position(b, c),
|
|
351
|
+
Delta=Position(d, e)))
|
|
352
|
+
elif event == _HAT:
|
|
353
|
+
self.HatSwitchPressed(self, Joystick.HatSwitchPressedEventArgs(Direction=HatSwitchDirection(a)))
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class PrinterStatus(IntFlag):
|
|
357
|
+
"""What a printer reports about itself (Ready: nothing)"""
|
|
358
|
+
|
|
359
|
+
Ready = 0
|
|
360
|
+
PaperOut = 0x01
|
|
361
|
+
Offline = 0x02
|
|
362
|
+
Error = 0x04
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class Printer(BaseDevice):
|
|
366
|
+
"""A USB printer: the bytes it understands (ESC/POS for receipt printers, ZPL or
|
|
367
|
+
EPL for label printers, PCL or PostScript for office printers) go to it as
|
|
368
|
+
they are.
|
|
369
|
+
|
|
370
|
+
printer = Printer(e.DeviceName) # from UsbHostController, DeviceType.Printer
|
|
371
|
+
printer.Write("Hello\n\n\n")
|
|
372
|
+
printer.Write(bytes([0x1D, 0x56, 0x00])) # ESC/POS: cut the paper
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
def __init__(self, deviceName):
|
|
376
|
+
super().__init__(deviceName, DeviceType.Printer)
|
|
377
|
+
self.WriteTimeout = -1
|
|
378
|
+
"""How long Write waits for the printer to take the data, in milliseconds (-1: as long as it takes)"""
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def DeviceId(self):
|
|
382
|
+
"""The printer's IEEE 1284 Device ID: "MFG:EPSON;CMD:ESC/POS;MDL:TM-T20II;CLS:PRINTER;" """
|
|
383
|
+
text = ctypes.create_string_buffer(1024)
|
|
384
|
+
native.printer_device_id(self._h(), text, len(text))
|
|
385
|
+
return text.value.decode(errors="replace")
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def Status(self):
|
|
389
|
+
"""PaperOut, Offline and Error, as the printer reports them (Ready: none)"""
|
|
390
|
+
status = native.out_int()
|
|
391
|
+
native.printer_status(self._h(), native.ref(status))
|
|
392
|
+
return PrinterStatus(status.value)
|
|
393
|
+
|
|
394
|
+
def Write(self, data):
|
|
395
|
+
"""Sends bytes (or text, as UTF-8) to the printer; all of them, or a TimeoutException
|
|
396
|
+
after WriteTimeout"""
|
|
397
|
+
if isinstance(data, str):
|
|
398
|
+
data = data.encode()
|
|
399
|
+
pointer, length, keep = native.readable(data)
|
|
400
|
+
written = native.out_int()
|
|
401
|
+
native.printer_write(self._h(), pointer, length, int(self.WriteTimeout), native.ref(written))
|
|
402
|
+
return written.value
|
|
403
|
+
|
|
404
|
+
def Read(self, buffer, timeout=-1):
|
|
405
|
+
"""Reads what the printer answers (to a status request) into a bytearray: its length,
|
|
406
|
+
0 if nothing came in timeout milliseconds (-1: as long as it takes)"""
|
|
407
|
+
pointer, size = native.writable(buffer)
|
|
408
|
+
length = native.out_int()
|
|
409
|
+
native.printer_read(self._h(), pointer, size, int(timeout), native.ref(length))
|
|
410
|
+
return length.value
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""The types of TinyCLR's USB host (GHIElectronics.TinyCLR.Devices.Usb): the
|
|
2
|
+
same names and values (Key is the USB key code). Python's None is a keyword:
|
|
3
|
+
Buttons.None_, Key.None_, HatSwitchDirection.None_."""
|
|
4
|
+
|
|
5
|
+
from enum import IntEnum, IntFlag
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DeviceType(IntEnum):
|
|
9
|
+
"""What a device gives, and its DeviceName"""
|
|
10
|
+
|
|
11
|
+
Unknown = 0
|
|
12
|
+
"""No driver on the board: the program talks to it with pyusb ("/dev/bus/usb/001/005"):
|
|
13
|
+
usb.core.find(idVendor=e.VendorId, idProduct=e.ProductId)"""
|
|
14
|
+
Hub = 1
|
|
15
|
+
""""/dev/bus/usb/001/002" """
|
|
16
|
+
HID = 2
|
|
17
|
+
"""A HID device that is no keyboard, mouse or joystick ("/dev/hidraw0"): use the hid
|
|
18
|
+
package (hidapi), hid.Device(vid=e.VendorId, pid=e.ProductId)"""
|
|
19
|
+
Mouse = 3
|
|
20
|
+
""""/dev/input/event2": open it with Mouse(e.DeviceName)"""
|
|
21
|
+
Keyboard = 4
|
|
22
|
+
""""/dev/input/event1": open it with Keyboard(e.DeviceName)"""
|
|
23
|
+
Joystick = 5
|
|
24
|
+
"""Joysticks and gamepads ("/dev/input/event3"): open it with Joystick(e.DeviceName)"""
|
|
25
|
+
MassStorage = 6
|
|
26
|
+
"""A drive: use StorageController (no name)"""
|
|
27
|
+
Printer = 7
|
|
28
|
+
""""/dev/usb/lp0": open it with Printer(e.DeviceName)"""
|
|
29
|
+
Serial = 8
|
|
30
|
+
"""A serial port, for pyserial ("/dev/ttyUSB0", "/dev/ttyACM0")"""
|
|
31
|
+
Webcam = 9
|
|
32
|
+
""""/dev/video0": open it with UsbCamera (GHIElectronics.Endpoint.Devices.Camera)"""
|
|
33
|
+
Audio = 10
|
|
34
|
+
"""A sound card: speaker, microphone ("hw:1")"""
|
|
35
|
+
Network = 11
|
|
36
|
+
"""An Ethernet, WiFi or cellular adapter: the network interface ("eth1", "wlan1")"""
|
|
37
|
+
Bluetooth = 12
|
|
38
|
+
""""hci0" """
|
|
39
|
+
Touch = 13
|
|
40
|
+
""""/dev/input/eventN": open it with Touch (built-in or USB touchscreen)"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Buttons(IntFlag):
|
|
44
|
+
"""The mouse buttons"""
|
|
45
|
+
|
|
46
|
+
None_ = 0x00
|
|
47
|
+
Left = 0x01
|
|
48
|
+
Right = 0x02
|
|
49
|
+
Middle = 0x04
|
|
50
|
+
Extended1 = 0x08
|
|
51
|
+
Extended2 = 0x10
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ButtonState(IntEnum):
|
|
55
|
+
Released = 0
|
|
56
|
+
Pressed = 1
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Cursor(IntEnum):
|
|
60
|
+
"""A joystick's sticks: Cursor1 (left), Cursor2 (right)"""
|
|
61
|
+
|
|
62
|
+
Cursor1 = 0
|
|
63
|
+
Cursor2 = 1
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class KeyState(IntEnum):
|
|
67
|
+
Released = 2
|
|
68
|
+
Pressed = 1
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class HatSwitchDirection(IntEnum):
|
|
72
|
+
Up = 0
|
|
73
|
+
UpRight = 1
|
|
74
|
+
Right = 2
|
|
75
|
+
DownRight = 3
|
|
76
|
+
Down = 4
|
|
77
|
+
DownLeft = 5
|
|
78
|
+
Left = 6
|
|
79
|
+
UpLeft = 7
|
|
80
|
+
None_ = 8
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Key(IntEnum):
|
|
84
|
+
"""The keys on the keyboard: USB key codes. D0 to D9 are the number keys. A key
|
|
85
|
+
TinyCLR has no name for (F13, media keys) comes as its USB key code (an int)."""
|
|
86
|
+
|
|
87
|
+
None_ = 0
|
|
88
|
+
A = 4
|
|
89
|
+
B = 5
|
|
90
|
+
C = 6
|
|
91
|
+
D = 7
|
|
92
|
+
E = 8
|
|
93
|
+
F = 9
|
|
94
|
+
G = 10
|
|
95
|
+
H = 11
|
|
96
|
+
I = 12
|
|
97
|
+
J = 13
|
|
98
|
+
K = 14
|
|
99
|
+
L = 15
|
|
100
|
+
M = 16
|
|
101
|
+
N = 17
|
|
102
|
+
O = 18
|
|
103
|
+
P = 19
|
|
104
|
+
Q = 20
|
|
105
|
+
R = 21
|
|
106
|
+
S = 22
|
|
107
|
+
T = 23
|
|
108
|
+
U = 24
|
|
109
|
+
V = 25
|
|
110
|
+
W = 26
|
|
111
|
+
X = 27
|
|
112
|
+
Y = 28
|
|
113
|
+
Z = 29
|
|
114
|
+
D1 = 30
|
|
115
|
+
D2 = 31
|
|
116
|
+
D3 = 32
|
|
117
|
+
D4 = 33
|
|
118
|
+
D5 = 34
|
|
119
|
+
D6 = 35
|
|
120
|
+
D7 = 36
|
|
121
|
+
D8 = 37
|
|
122
|
+
D9 = 38
|
|
123
|
+
D0 = 39
|
|
124
|
+
Enter = 40
|
|
125
|
+
Escape = 41
|
|
126
|
+
BackSpace = 42
|
|
127
|
+
Tab = 43
|
|
128
|
+
Space = 44
|
|
129
|
+
Substract = 45
|
|
130
|
+
Equal = 46
|
|
131
|
+
OpenBrackets = 47
|
|
132
|
+
CloseBrackets = 48
|
|
133
|
+
Backslash = 49
|
|
134
|
+
NonUS = 50
|
|
135
|
+
Semicolon = 51
|
|
136
|
+
Quotes = 52
|
|
137
|
+
GraveAccent = 53
|
|
138
|
+
Comma = 54
|
|
139
|
+
Period = 55
|
|
140
|
+
Divide = 56
|
|
141
|
+
CapsLock = 57
|
|
142
|
+
F1 = 58
|
|
143
|
+
F2 = 59
|
|
144
|
+
F3 = 60
|
|
145
|
+
F4 = 61
|
|
146
|
+
F5 = 62
|
|
147
|
+
F6 = 63
|
|
148
|
+
F7 = 64
|
|
149
|
+
F8 = 65
|
|
150
|
+
F9 = 66
|
|
151
|
+
F10 = 67
|
|
152
|
+
F11 = 68
|
|
153
|
+
F12 = 69
|
|
154
|
+
PrintScreen = 70
|
|
155
|
+
ScrollLock = 71
|
|
156
|
+
Pause = 72
|
|
157
|
+
Insert = 73
|
|
158
|
+
Home = 74
|
|
159
|
+
PageUp = 75
|
|
160
|
+
Delete = 76
|
|
161
|
+
End = 77
|
|
162
|
+
PageDown = 78
|
|
163
|
+
RightArrow = 79
|
|
164
|
+
LeftArrow = 80
|
|
165
|
+
DownArrow = 81
|
|
166
|
+
UpArrow = 82
|
|
167
|
+
NumLock = 83
|
|
168
|
+
KeypadDivide = 84
|
|
169
|
+
KeypadMultiply = 85
|
|
170
|
+
KeypadSubstract = 86
|
|
171
|
+
KeypadAdd = 87
|
|
172
|
+
KeypadEnter = 88
|
|
173
|
+
Keypad1 = 89
|
|
174
|
+
Keypad2 = 90
|
|
175
|
+
Keypad3 = 91
|
|
176
|
+
Keypad4 = 92
|
|
177
|
+
Keypad5 = 93
|
|
178
|
+
Keypad6 = 94
|
|
179
|
+
Keypad7 = 95
|
|
180
|
+
Keypad8 = 96
|
|
181
|
+
Keypad9 = 97
|
|
182
|
+
Keypad0 = 98
|
|
183
|
+
KeypadDelete = 99
|
|
184
|
+
NonUS2 = 100
|
|
185
|
+
Application = 101
|
|
186
|
+
LeftCtrl = 0xE0
|
|
187
|
+
LeftShift = 0xE1
|
|
188
|
+
LeftAlt = 0xE2
|
|
189
|
+
LeftGUI = 0xE3
|
|
190
|
+
RightCtrl = 0xE4
|
|
191
|
+
RightShift = 0xE5
|
|
192
|
+
RightAlt = 0xE6
|
|
193
|
+
RightGUI = 0xE7
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class Position:
|
|
197
|
+
"""A position: X, Y (and Z)"""
|
|
198
|
+
|
|
199
|
+
def __init__(self, X=0, Y=0, Z=0):
|
|
200
|
+
self.X = X
|
|
201
|
+
self.Y = Y
|
|
202
|
+
self.Z = Z
|
|
203
|
+
|
|
204
|
+
def __sub__(self, other):
|
|
205
|
+
return Position(self.X - other.X, self.Y - other.Y, self.Z - other.Z)
|
|
206
|
+
|
|
207
|
+
def __eq__(self, other):
|
|
208
|
+
return isinstance(other, Position) and (self.X, self.Y, self.Z) == (other.X, other.Y, other.Z)
|
|
209
|
+
|
|
210
|
+
def __hash__(self):
|
|
211
|
+
return hash((self.X, self.Y, self.Z))
|
|
212
|
+
|
|
213
|
+
def __str__(self):
|
|
214
|
+
return f"({self.X}, {self.Y}, {self.Z})"
|
|
215
|
+
|
|
216
|
+
def __repr__(self):
|
|
217
|
+
return f"Position({self.X}, {self.Y}, {self.Z})"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: GHIElectronics.Endpoint.Devices.UsbHost
|
|
3
|
+
Version: 0.5.3
|
|
4
|
+
Summary: USB devices of GHI Electronics Endpoint: connected and disconnected events, what each device is and its port or network interface.
|
|
5
|
+
Author-email: "GHI Electronics, LLC" <support@ghielectronics.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://www.ghielectronics.com/
|
|
8
|
+
Project-URL: Repository, https://github.com/ghi-electronics/Endpoint-Libraries
|
|
9
|
+
Project-URL: Issues, https://github.com/ghi-electronics/Endpoint-Libraries/issues
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: GHIElectronics.Endpoint.Core<0.6,>=0.5.3
|
|
15
|
+
|
|
16
|
+
# GHIElectronics.Endpoint.Devices.UsbHost
|
|
17
|
+
|
|
18
|
+
USB devices of GHI Electronics Endpoint: connected and disconnected events, what each device is and its port or network interface.
|
|
19
|
+
|
|
20
|
+
Part of the [GHI Electronics Endpoint](https://github.com/ghi-electronics/Endpoint-Libraries) libraries. See the repository README for the full package list and getting-started guide.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
GHIElectronics/Endpoint/Devices/UsbHost/__init__.py,sha256=gzNOm5rEgbFOVaEOA_6g9Mefp7feWWkRcM75X7mLXho,5764
|
|
2
|
+
GHIElectronics/Endpoint/Devices/UsbHost/_input.py,sha256=nPfnZp1lenmXX_sUVq1XEVV0qdi6UVRA5HHXO9eNup4,14972
|
|
3
|
+
GHIElectronics/Endpoint/Devices/UsbHost/_types.py,sha256=MVbgBPxptDKc6msY9Z5DWCtnA3BFzFwHWZ5GyL3aDm0,4633
|
|
4
|
+
ghielectronics_endpoint_devices_usbhost-0.5.3.dist-info/METADATA,sha256=dxi9liDMyT5LjzZvG3czKtGmq3DwfXu9xJm_VNjEcwA,1123
|
|
5
|
+
ghielectronics_endpoint_devices_usbhost-0.5.3.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
6
|
+
ghielectronics_endpoint_devices_usbhost-0.5.3.dist-info/top_level.txt,sha256=NpOZNgd0iquwrRPrTOhUDxc2V9TtNFLv5Z_Dx3YZtIU,15
|
|
7
|
+
ghielectronics_endpoint_devices_usbhost-0.5.3.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
GHIElectronics
|