pych9329 0.1.5__tar.gz
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.
- pych9329-0.1.5/LICENSE +21 -0
- pych9329-0.1.5/PKG-INFO +81 -0
- pych9329-0.1.5/README.md +63 -0
- pych9329-0.1.5/pyproject.toml +23 -0
- pych9329-0.1.5/src/pych9329/__init__.py +0 -0
- pych9329-0.1.5/src/pych9329/chip_command.py +200 -0
- pych9329-0.1.5/src/pych9329/exceptions.py +50 -0
- pych9329-0.1.5/src/pych9329/frame_utils.py +99 -0
- pych9329-0.1.5/src/pych9329/hid_code_map.py +190 -0
- pych9329-0.1.5/src/pych9329/hid_code_table.py +161 -0
- pych9329-0.1.5/src/pych9329/keyboard.py +216 -0
- pych9329-0.1.5/src/pych9329/mouse.py +159 -0
- pych9329-0.1.5/src/pych9329/py.typed +0 -0
- pych9329-0.1.5/src/pych9329/struct_utils.py +95 -0
pych9329-0.1.5/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 wevsty
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pych9329-0.1.5/PKG-INFO
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pych9329
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Python module to control the ch9329
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: wevsty
|
|
7
|
+
Author-email: ty@wevs.org
|
|
8
|
+
Requires-Python: >3.10
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Dist: pyserial (>=3.5,<4.0)
|
|
15
|
+
Project-URL: Repository, https://github.com/wevsty/pych9329
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# pych9329
|
|
19
|
+
Python module to control ch9329
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
You can install the package via pip:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install pych9329
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
from serial import Serial
|
|
33
|
+
|
|
34
|
+
from pych9329 import keyboard
|
|
35
|
+
from pych9329 import mouse
|
|
36
|
+
from pych9329.chip_command import get_manufacturer
|
|
37
|
+
from pych9329.chip_command import get_product
|
|
38
|
+
from pych9329.chip_command import get_serial_number
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main():
|
|
42
|
+
ser = Serial("COM10", 9600, timeout=0.50)
|
|
43
|
+
|
|
44
|
+
keyboard.click(ser, "a")
|
|
45
|
+
keyboard.click(ser, "A")
|
|
46
|
+
keyboard.send_text_data(ser, "\n")
|
|
47
|
+
keyboard.send_text_data(ser, "Hello World\n")
|
|
48
|
+
keyboard.send_text_data(ser, "abcdefghijklmnopqrstuvwxyz\n")
|
|
49
|
+
keyboard.send_text_data(ser, "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n")
|
|
50
|
+
keyboard.send_text_data(ser, "0123456789\n")
|
|
51
|
+
keyboard.send_text_data(ser, "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\n")
|
|
52
|
+
|
|
53
|
+
mouse.move(ser, x=500, y=500)
|
|
54
|
+
mouse.move(ser, x=50, y=50, relative_mode=True)
|
|
55
|
+
# For supported key names, please see mouse.MOUSE_BUTTON_NAME_MAP
|
|
56
|
+
mouse.click(ser, button_name="left")
|
|
57
|
+
|
|
58
|
+
# 2019A152BB40
|
|
59
|
+
print(get_serial_number(ser))
|
|
60
|
+
# WCH UART TO KB-MS_V1.8
|
|
61
|
+
print(get_product(ser))
|
|
62
|
+
# WWW.WCH.CN
|
|
63
|
+
print(get_manufacturer(ser))
|
|
64
|
+
ser.close()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
main()
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT license.
|
|
75
|
+
|
|
76
|
+
## Contributing
|
|
77
|
+
|
|
78
|
+
This project is modified from ch9329 library.
|
|
79
|
+
|
|
80
|
+
Thanks to the original author of the ch9329 library [Pradish Bijukchhe](https://github.com/pradishb)
|
|
81
|
+
|
pych9329-0.1.5/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# pych9329
|
|
2
|
+
Python module to control ch9329
|
|
3
|
+
|
|
4
|
+
## Installation
|
|
5
|
+
|
|
6
|
+
You can install the package via pip:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install pych9329
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```py
|
|
15
|
+
from serial import Serial
|
|
16
|
+
|
|
17
|
+
from pych9329 import keyboard
|
|
18
|
+
from pych9329 import mouse
|
|
19
|
+
from pych9329.chip_command import get_manufacturer
|
|
20
|
+
from pych9329.chip_command import get_product
|
|
21
|
+
from pych9329.chip_command import get_serial_number
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main():
|
|
25
|
+
ser = Serial("COM10", 9600, timeout=0.50)
|
|
26
|
+
|
|
27
|
+
keyboard.click(ser, "a")
|
|
28
|
+
keyboard.click(ser, "A")
|
|
29
|
+
keyboard.send_text_data(ser, "\n")
|
|
30
|
+
keyboard.send_text_data(ser, "Hello World\n")
|
|
31
|
+
keyboard.send_text_data(ser, "abcdefghijklmnopqrstuvwxyz\n")
|
|
32
|
+
keyboard.send_text_data(ser, "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n")
|
|
33
|
+
keyboard.send_text_data(ser, "0123456789\n")
|
|
34
|
+
keyboard.send_text_data(ser, "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\n")
|
|
35
|
+
|
|
36
|
+
mouse.move(ser, x=500, y=500)
|
|
37
|
+
mouse.move(ser, x=50, y=50, relative_mode=True)
|
|
38
|
+
# For supported key names, please see mouse.MOUSE_BUTTON_NAME_MAP
|
|
39
|
+
mouse.click(ser, button_name="left")
|
|
40
|
+
|
|
41
|
+
# 2019A152BB40
|
|
42
|
+
print(get_serial_number(ser))
|
|
43
|
+
# WCH UART TO KB-MS_V1.8
|
|
44
|
+
print(get_product(ser))
|
|
45
|
+
# WWW.WCH.CN
|
|
46
|
+
print(get_manufacturer(ser))
|
|
47
|
+
ser.close()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
main()
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
MIT license.
|
|
58
|
+
|
|
59
|
+
## Contributing
|
|
60
|
+
|
|
61
|
+
This project is modified from ch9329 library.
|
|
62
|
+
|
|
63
|
+
Thanks to the original author of the ch9329 library [Pradish Bijukchhe](https://github.com/pradishb)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "pych9329"
|
|
3
|
+
version = "0.1.5"
|
|
4
|
+
description = "Python module to control the ch9329"
|
|
5
|
+
authors = ["wevsty <ty@wevs.org>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
repository = "https://github.com/wevsty/pych9329"
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = ">3.10"
|
|
12
|
+
pyserial = "^3.5"
|
|
13
|
+
|
|
14
|
+
[tool.poetry.group.dev.dependencies]
|
|
15
|
+
black = "^25.1.0"
|
|
16
|
+
isort = "^6.0.1"
|
|
17
|
+
pytest = "^8.3.5"
|
|
18
|
+
pytest-mock = "^3.14.0"
|
|
19
|
+
pytest-datadir = "^1.6.1"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["poetry-core"]
|
|
23
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from serial import Serial
|
|
4
|
+
|
|
5
|
+
import pych9329.exceptions as exceptions
|
|
6
|
+
import pych9329.frame_utils as frame_utils
|
|
7
|
+
from pych9329.struct_utils import Structure
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ChipParameter(Structure):
|
|
11
|
+
_fields_ = [
|
|
12
|
+
('c', 'work_mode'),
|
|
13
|
+
('c', 'serial_communication_mode'),
|
|
14
|
+
('c', 'serial_address'),
|
|
15
|
+
('>I', 'serial_baud_rate'),
|
|
16
|
+
('>H', 'reserve_00'),
|
|
17
|
+
('>H', 'communication_packet_interval'),
|
|
18
|
+
('<H', 'usb_vid'),
|
|
19
|
+
('<H', 'usb_pid'),
|
|
20
|
+
('>H', 'keyboard_upload_interval'),
|
|
21
|
+
('>H', 'keyboard_release_delay'),
|
|
22
|
+
('>H', 'keyboard_auto_enter_flag'),
|
|
23
|
+
('4s', 'keyboard_enter_data_1'),
|
|
24
|
+
('4s', 'keyboard_enter_data_2'),
|
|
25
|
+
('4s', 'keyboard_filtering_start'),
|
|
26
|
+
('c', 'usb_string_descriptor_flag'),
|
|
27
|
+
('4s', 'keyboard_fast_upload_flag'),
|
|
28
|
+
('12s', 'reserve_01'),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_chip_parameters(serial_object: Serial) -> ChipParameter:
|
|
33
|
+
# request frame
|
|
34
|
+
data_frame = frame_utils.DataFrame(
|
|
35
|
+
b'\x57\xab',
|
|
36
|
+
b'\x00',
|
|
37
|
+
b'\x08',
|
|
38
|
+
b'\x00',
|
|
39
|
+
b'',
|
|
40
|
+
)
|
|
41
|
+
request_packet = data_frame.create_frame()
|
|
42
|
+
serial_object.write(request_packet)
|
|
43
|
+
serial_object.flush()
|
|
44
|
+
# Reply frame should be 56 bytes
|
|
45
|
+
reply_length = 56
|
|
46
|
+
reply_buffer = serial_object.read(reply_length)
|
|
47
|
+
result = data_frame.parse_frame(reply_buffer)
|
|
48
|
+
if result is False:
|
|
49
|
+
raise exceptions.ProtocolError('parser buffer error')
|
|
50
|
+
chip_parameter = ChipParameter.from_buffer(data_frame.get_data())
|
|
51
|
+
return chip_parameter
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def set_chip_parameters(serial_object: Serial, parameter: ChipParameter) -> bool:
|
|
55
|
+
parameter.buffer_flush()
|
|
56
|
+
parameter_buffer = parameter.buffer
|
|
57
|
+
# request frame
|
|
58
|
+
data_frame = frame_utils.DataFrame(
|
|
59
|
+
b'\x57\xab',
|
|
60
|
+
b'\x00',
|
|
61
|
+
b'\x09',
|
|
62
|
+
b'\x00',
|
|
63
|
+
b'\x00',
|
|
64
|
+
)
|
|
65
|
+
data_frame.set_data(parameter_buffer)
|
|
66
|
+
request_packet = data_frame.create_frame()
|
|
67
|
+
serial_object.write(request_packet)
|
|
68
|
+
serial_object.flush()
|
|
69
|
+
# Reply frame should be 7 bytes
|
|
70
|
+
reply_length = 7
|
|
71
|
+
reply_buffer = serial_object.read(reply_length)
|
|
72
|
+
result = data_frame.parse_frame(reply_buffer)
|
|
73
|
+
if result is False:
|
|
74
|
+
raise exceptions.ProtocolError('parser buffer error')
|
|
75
|
+
reply_data = data_frame.get_data()
|
|
76
|
+
if reply_data == frame_utils.DataFrameStatus.SUCCESS.value:
|
|
77
|
+
return True
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class USBStringSubCommand(Enum):
|
|
82
|
+
MANUFACTURER = b'\x00'
|
|
83
|
+
PRODUCT = b'\x01'
|
|
84
|
+
SERIAL_NUMBER = b'\x02'
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_usb_string_info(serial_object: Serial, sub_command: bytes) -> str:
|
|
88
|
+
data_frame = frame_utils.DataFrame(
|
|
89
|
+
b'\x57\xab',
|
|
90
|
+
b'\x00',
|
|
91
|
+
b'\x0a',
|
|
92
|
+
b'\x01',
|
|
93
|
+
sub_command,
|
|
94
|
+
)
|
|
95
|
+
request_packet = data_frame.create_frame()
|
|
96
|
+
serial_object.readall()
|
|
97
|
+
serial_object.write(request_packet)
|
|
98
|
+
serial_object.flush()
|
|
99
|
+
reply_buffer = serial_object.readall()
|
|
100
|
+
result = data_frame.parse_frame(reply_buffer)
|
|
101
|
+
if result is False:
|
|
102
|
+
raise exceptions.ProtocolError('parser buffer error')
|
|
103
|
+
if data_frame.get_data_length() < 2:
|
|
104
|
+
raise exceptions.ProtocolError('parser buffer error')
|
|
105
|
+
data_buffer = data_frame.get_data()
|
|
106
|
+
# string_length = data_buffer[1]
|
|
107
|
+
string_bytes = data_buffer[2:]
|
|
108
|
+
# usb_info = string_bytes.decode('utf-8', 'ignore')
|
|
109
|
+
try:
|
|
110
|
+
usb_info = string_bytes.decode('utf-8')
|
|
111
|
+
except UnicodeDecodeError:
|
|
112
|
+
raise exceptions.InvalidStringEncoding('unable decode string')
|
|
113
|
+
return usb_info
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def set_usb_string_info(
|
|
117
|
+
serial_object: Serial, sub_command: bytes, string_info: str
|
|
118
|
+
) -> bool:
|
|
119
|
+
info_data = string_info.encode('utf-8')
|
|
120
|
+
if len(info_data) > 23:
|
|
121
|
+
raise InvalidStringLength('string too long')
|
|
122
|
+
data_buffer = (
|
|
123
|
+
sub_command + int.to_bytes(len(info_data), byteorder='big') + info_data
|
|
124
|
+
)
|
|
125
|
+
data_frame = frame_utils.DataFrame(
|
|
126
|
+
b'\x57\xab',
|
|
127
|
+
b'\x00',
|
|
128
|
+
b'\x0b',
|
|
129
|
+
b'',
|
|
130
|
+
b'',
|
|
131
|
+
)
|
|
132
|
+
data_frame.set_data(data_buffer)
|
|
133
|
+
request_packet = data_frame.create_frame()
|
|
134
|
+
serial_object.readall()
|
|
135
|
+
serial_object.write(request_packet)
|
|
136
|
+
serial_object.flush()
|
|
137
|
+
reply_buffer = serial_object.readall()
|
|
138
|
+
result = data_frame.parse_frame(reply_buffer)
|
|
139
|
+
if result is False:
|
|
140
|
+
raise exceptions.ProtocolError('parser buffer error')
|
|
141
|
+
reply_data = data_frame.get_data()
|
|
142
|
+
if reply_data == frame_utils.DataFrameStatus.SUCCESS.value:
|
|
143
|
+
return True
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def get_serial_number(serial_object: Serial) -> str:
|
|
148
|
+
return get_usb_string_info(serial_object, USBStringSubCommand.SERIAL_NUMBER.value)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def set_serial_number(serial_object: Serial, string_data: str):
|
|
152
|
+
return set_usb_string_info(
|
|
153
|
+
serial_object, USBStringSubCommand.SERIAL_NUMBER.value, string_data
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def get_manufacturer(serial_object: Serial) -> str:
|
|
158
|
+
return get_usb_string_info(serial_object, USBStringSubCommand.MANUFACTURER.value)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def set_manufacturer(serial_object: Serial, string_data: str):
|
|
162
|
+
return set_usb_string_info(
|
|
163
|
+
serial_object, USBStringSubCommand.MANUFACTURER.value, string_data
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def get_product(serial_object: Serial) -> str:
|
|
168
|
+
return get_usb_string_info(serial_object, USBStringSubCommand.PRODUCT.value)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def set_product(serial_object: Serial, string_data: str):
|
|
172
|
+
return set_usb_string_info(
|
|
173
|
+
serial_object, USBStringSubCommand.PRODUCT.value, string_data
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def send_command_reset(serial_object: Serial) -> None:
|
|
178
|
+
data_frame = frame_utils.DataFrame(
|
|
179
|
+
b'\x57\xab',
|
|
180
|
+
b'\x00',
|
|
181
|
+
b'\x0f',
|
|
182
|
+
b'\x00',
|
|
183
|
+
b'',
|
|
184
|
+
)
|
|
185
|
+
request_packet = data_frame.create_frame()
|
|
186
|
+
serial_object.write(request_packet)
|
|
187
|
+
|
|
188
|
+
def send_command_restore_factory_config(serial_object: Serial) -> None:
|
|
189
|
+
data_frame = frame_utils.DataFrame(
|
|
190
|
+
b'\x57\xab',
|
|
191
|
+
b'\x00',
|
|
192
|
+
b'\x0c',
|
|
193
|
+
b'\x00',
|
|
194
|
+
b'',
|
|
195
|
+
)
|
|
196
|
+
request_packet = data_frame.create_frame()
|
|
197
|
+
serial_object.write(request_packet)
|
|
198
|
+
|
|
199
|
+
if __name__ == '__main__':
|
|
200
|
+
pass
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class ChipBaseException(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# 协议错误
|
|
6
|
+
class ProtocolError(ChipBaseException):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# 无效的字符串编码
|
|
11
|
+
class InvalidStringEncoding(ChipBaseException):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# 无效的字符串长度
|
|
16
|
+
class InvalidStringLength(ChipBaseException):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# 无效的坐标值
|
|
21
|
+
class InvalidCoordinateValue(ChipBaseException):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# 无效的滚轮值
|
|
26
|
+
class InvalidWheelValue(ChipBaseException):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# 无效的特殊按键
|
|
31
|
+
class InvalidModifierKey(ChipBaseException):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# 无效的按键
|
|
36
|
+
class InvalidKey(ChipBaseException):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# 传入太多按键
|
|
41
|
+
class TooManyKeys(ChipBaseException):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# 缓冲区太小
|
|
46
|
+
class BufferTooSmall(ChipBaseException):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
if __name__ == '__main__':
|
|
50
|
+
pass
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DataFrameStatus(Enum):
|
|
6
|
+
SUCCESS = b'\x00'
|
|
7
|
+
ERROR_TIMEOUT = b'\xe1'
|
|
8
|
+
ERROR_HEAD = b'\xe2'
|
|
9
|
+
ERROR_CMD = b'\xe3'
|
|
10
|
+
ERROR_SUM = b'\xe4'
|
|
11
|
+
ERROR_PARA = b'\xe5'
|
|
12
|
+
ERROR_OPERATE = b'\xe6'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class DataFrame:
|
|
17
|
+
HEAD: bytes
|
|
18
|
+
ADDR: bytes
|
|
19
|
+
CMD: bytes
|
|
20
|
+
LEN: bytes = b'\x00'
|
|
21
|
+
DATA: bytes = b''
|
|
22
|
+
|
|
23
|
+
def create_frame(self) -> bytes:
|
|
24
|
+
checksum = self.calc_checksum()
|
|
25
|
+
frame = (
|
|
26
|
+
self.HEAD + self.ADDR + self.CMD + self.LEN + self.DATA + bytes([checksum])
|
|
27
|
+
)
|
|
28
|
+
return frame
|
|
29
|
+
|
|
30
|
+
def get_data(self) -> bytes:
|
|
31
|
+
return self.DATA
|
|
32
|
+
|
|
33
|
+
def get_data_length(self) -> int:
|
|
34
|
+
length = int.from_bytes(self.LEN, byteorder='big')
|
|
35
|
+
return length
|
|
36
|
+
|
|
37
|
+
def set_data(self, data: bytes) -> None:
|
|
38
|
+
self.DATA = data
|
|
39
|
+
self.set_data_length(len(data))
|
|
40
|
+
|
|
41
|
+
def set_data_length(self, length: int) -> None:
|
|
42
|
+
self.LEN = int.to_bytes(length, byteorder='big')
|
|
43
|
+
|
|
44
|
+
def calc_frame_length(self, buffer: bytes) -> int:
|
|
45
|
+
header_size = 5
|
|
46
|
+
frame_length = 0
|
|
47
|
+
buffer_length = len(buffer)
|
|
48
|
+
if buffer_length < header_size:
|
|
49
|
+
return frame_length
|
|
50
|
+
head_first = buffer[0:1]
|
|
51
|
+
head_last = buffer[1:2]
|
|
52
|
+
self.ADDR = buffer[2:3]
|
|
53
|
+
self.CMD = buffer[3:4]
|
|
54
|
+
self.LEN = buffer[4:5]
|
|
55
|
+
self.HEAD = head_first + head_last
|
|
56
|
+
data_length = self.get_data_length()
|
|
57
|
+
frame_length = header_size + data_length + 1
|
|
58
|
+
return frame_length
|
|
59
|
+
|
|
60
|
+
def calc_checksum(self) -> int:
|
|
61
|
+
head_list = list(self.HEAD)
|
|
62
|
+
head_sum = sum(head_list)
|
|
63
|
+
data_list = list(self.DATA)
|
|
64
|
+
data_sum = sum(data_list)
|
|
65
|
+
checksum = (
|
|
66
|
+
sum(
|
|
67
|
+
[
|
|
68
|
+
head_sum,
|
|
69
|
+
int.from_bytes(self.ADDR, byteorder='big'),
|
|
70
|
+
int.from_bytes(self.CMD, byteorder='big'),
|
|
71
|
+
int.from_bytes(self.LEN, byteorder='big'),
|
|
72
|
+
data_sum,
|
|
73
|
+
]
|
|
74
|
+
)
|
|
75
|
+
% 256
|
|
76
|
+
)
|
|
77
|
+
return checksum
|
|
78
|
+
|
|
79
|
+
def parse_frame(self, buffer: bytes) -> bool:
|
|
80
|
+
header_size = 5
|
|
81
|
+
parse_result = False
|
|
82
|
+
buffer_length = len(buffer)
|
|
83
|
+
if buffer_length < header_size:
|
|
84
|
+
return parse_result
|
|
85
|
+
frame_length = self.calc_frame_length(buffer)
|
|
86
|
+
if buffer_length < frame_length:
|
|
87
|
+
return parse_result
|
|
88
|
+
data_length = self.get_data_length()
|
|
89
|
+
if data_length >= 0:
|
|
90
|
+
self.DATA = buffer[header_size : header_size + data_length]
|
|
91
|
+
_checksum = buffer[
|
|
92
|
+
header_size + data_length : header_size + data_length + 1
|
|
93
|
+
]
|
|
94
|
+
parse_result = True
|
|
95
|
+
return parse_result
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == '__main__':
|
|
99
|
+
pass
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import pych9329.hid_code_table as hid_code
|
|
2
|
+
|
|
3
|
+
HID_CODE_MAP = {
|
|
4
|
+
'': (hid_code.KEY_NONE, False),
|
|
5
|
+
# release alias
|
|
6
|
+
'null': (hid_code.KEY_NONE, False),
|
|
7
|
+
'none': (hid_code.KEY_NONE, False),
|
|
8
|
+
'a': (hid_code.KEY_A, False),
|
|
9
|
+
'b': (hid_code.KEY_B, False),
|
|
10
|
+
'c': (hid_code.KEY_C, False),
|
|
11
|
+
'd': (hid_code.KEY_D, False),
|
|
12
|
+
'e': (hid_code.KEY_E, False),
|
|
13
|
+
'f': (hid_code.KEY_F, False),
|
|
14
|
+
'g': (hid_code.KEY_G, False),
|
|
15
|
+
'h': (hid_code.KEY_H, False),
|
|
16
|
+
'i': (hid_code.KEY_I, False),
|
|
17
|
+
'j': (hid_code.KEY_J, False),
|
|
18
|
+
'k': (hid_code.KEY_K, False),
|
|
19
|
+
'l': (hid_code.KEY_L, False),
|
|
20
|
+
'm': (hid_code.KEY_M, False),
|
|
21
|
+
'n': (hid_code.KEY_N, False),
|
|
22
|
+
'o': (hid_code.KEY_O, False),
|
|
23
|
+
'p': (hid_code.KEY_P, False),
|
|
24
|
+
'q': (hid_code.KEY_Q, False),
|
|
25
|
+
'r': (hid_code.KEY_R, False),
|
|
26
|
+
's': (hid_code.KEY_S, False),
|
|
27
|
+
't': (hid_code.KEY_T, False),
|
|
28
|
+
'u': (hid_code.KEY_U, False),
|
|
29
|
+
'v': (hid_code.KEY_V, False),
|
|
30
|
+
'w': (hid_code.KEY_W, False),
|
|
31
|
+
'x': (hid_code.KEY_X, False),
|
|
32
|
+
'y': (hid_code.KEY_Y, False),
|
|
33
|
+
'z': (hid_code.KEY_Z, False),
|
|
34
|
+
'1': (hid_code.KEY_1, False),
|
|
35
|
+
'2': (hid_code.KEY_2, False),
|
|
36
|
+
'3': (hid_code.KEY_3, False),
|
|
37
|
+
'4': (hid_code.KEY_4, False),
|
|
38
|
+
'5': (hid_code.KEY_5, False),
|
|
39
|
+
'6': (hid_code.KEY_6, False),
|
|
40
|
+
'7': (hid_code.KEY_7, False),
|
|
41
|
+
'8': (hid_code.KEY_8, False),
|
|
42
|
+
'9': (hid_code.KEY_9, False),
|
|
43
|
+
'0': (hid_code.KEY_0, False),
|
|
44
|
+
'enter': (hid_code.KEY_ENTER, False),
|
|
45
|
+
'esc': (hid_code.KEY_ESCAPE, False),
|
|
46
|
+
'escape': (hid_code.KEY_ESCAPE, False),
|
|
47
|
+
'backspace': (hid_code.KEY_BACKSPACE, False),
|
|
48
|
+
'tab': (hid_code.KEY_TAB, False),
|
|
49
|
+
'space': (hid_code.KEY_SPACE, False),
|
|
50
|
+
'bracket_left': (hid_code.KEY_BRACKET_LEFT, False),
|
|
51
|
+
'bracket_right': (hid_code.KEY_BRACKET_RIGHT, False),
|
|
52
|
+
'backslash': (hid_code.KEY_CODE_29, False),
|
|
53
|
+
# 'europe_1': (hid_code.KEY_EUROPE_1, False),
|
|
54
|
+
'semicolon': (hid_code.KEY_SEMICOLON, False),
|
|
55
|
+
'apostrophe': (hid_code.KEY_APOSTROPHE, False),
|
|
56
|
+
'grave': (hid_code.KEY_GRAVE, False),
|
|
57
|
+
'comma': (hid_code.KEY_COMMA, False),
|
|
58
|
+
'period': (hid_code.KEY_PERIOD, False),
|
|
59
|
+
'slash': (hid_code.KEY_SLASH, False),
|
|
60
|
+
'caps_lock': (hid_code.KEY_CAPS_LOCK, False),
|
|
61
|
+
'f1': (hid_code.KEY_F1, False),
|
|
62
|
+
'f2': (hid_code.KEY_F2, False),
|
|
63
|
+
'f3': (hid_code.KEY_F3, False),
|
|
64
|
+
'f4': (hid_code.KEY_F4, False),
|
|
65
|
+
'f5': (hid_code.KEY_F5, False),
|
|
66
|
+
'f6': (hid_code.KEY_F6, False),
|
|
67
|
+
'f7': (hid_code.KEY_F7, False),
|
|
68
|
+
'f8': (hid_code.KEY_F8, False),
|
|
69
|
+
'f9': (hid_code.KEY_F9, False),
|
|
70
|
+
'f10': (hid_code.KEY_F10, False),
|
|
71
|
+
'f11': (hid_code.KEY_F11, False),
|
|
72
|
+
'f12': (hid_code.KEY_F12, False),
|
|
73
|
+
'print_screen': (hid_code.KEY_PRINT_SCREEN, False),
|
|
74
|
+
'scroll_lock': (hid_code.KEY_SCROLL_LOCK, False),
|
|
75
|
+
'pause': (hid_code.KEY_PAUSE, False),
|
|
76
|
+
'insert': (hid_code.KEY_INSERT, False),
|
|
77
|
+
'home': (hid_code.KEY_HOME, False),
|
|
78
|
+
'page_up': (hid_code.KEY_PAGE_UP, False),
|
|
79
|
+
'delete': (hid_code.KEY_DELETE, False),
|
|
80
|
+
'del': (hid_code.KEY_DELETE, False),
|
|
81
|
+
'end': (hid_code.KEY_END, False),
|
|
82
|
+
'page_down': (hid_code.KEY_PAGE_DOWN, False),
|
|
83
|
+
'arrow_right': (hid_code.KEY_ARROW_RIGHT, False),
|
|
84
|
+
'arrow_left': (hid_code.KEY_ARROW_LEFT, False),
|
|
85
|
+
'arrow_down': (hid_code.KEY_ARROW_DOWN, False),
|
|
86
|
+
'arrow_up': (hid_code.KEY_ARROW_UP, False),
|
|
87
|
+
'right': (hid_code.KEY_ARROW_RIGHT, False),
|
|
88
|
+
'left': (hid_code.KEY_ARROW_LEFT, False),
|
|
89
|
+
'down': (hid_code.KEY_ARROW_DOWN, False),
|
|
90
|
+
'up': (hid_code.KEY_ARROW_UP, False),
|
|
91
|
+
'num_lock': (hid_code.KEY_NUM_LOCK, False),
|
|
92
|
+
'keypad_divide': (hid_code.KEY_KEYPAD_DIVIDE, False),
|
|
93
|
+
'keypad_multiply': (hid_code.KEY_KEYPAD_MULTIPLY, False),
|
|
94
|
+
'keypad_subtract': (hid_code.KEY_KEYPAD_SUBTRACT, False),
|
|
95
|
+
'keypad_add': (hid_code.KEY_KEYPAD_ADD, False),
|
|
96
|
+
'keypad_enter': (hid_code.KEY_KEYPAD_ENTER, False),
|
|
97
|
+
'keypad_1': (hid_code.KEY_KEYPAD_1, False),
|
|
98
|
+
'keypad_2': (hid_code.KEY_KEYPAD_2, False),
|
|
99
|
+
'keypad_3': (hid_code.KEY_KEYPAD_3, False),
|
|
100
|
+
'keypad_4': (hid_code.KEY_KEYPAD_4, False),
|
|
101
|
+
'keypad_5': (hid_code.KEY_KEYPAD_5, False),
|
|
102
|
+
'keypad_6': (hid_code.KEY_KEYPAD_6, False),
|
|
103
|
+
'keypad_7': (hid_code.KEY_KEYPAD_7, False),
|
|
104
|
+
'keypad_8': (hid_code.KEY_KEYPAD_8, False),
|
|
105
|
+
'keypad_9': (hid_code.KEY_KEYPAD_9, False),
|
|
106
|
+
'keypad_0': (hid_code.KEY_KEYPAD_0, False),
|
|
107
|
+
'keypad_decimal': (hid_code.KEY_KEYPAD_DECIMAL, False),
|
|
108
|
+
# modifiers
|
|
109
|
+
'ctrl': (hid_code.KEY_CTRL_L, False),
|
|
110
|
+
'ctrl_left': (hid_code.KEY_CTRL_L, False),
|
|
111
|
+
'ctrl_right': (hid_code.KEY_CTRL_R, False),
|
|
112
|
+
'shift': (hid_code.KEY_SHIFT_L, False),
|
|
113
|
+
'shift_left': (hid_code.KEY_SHIFT_L, False),
|
|
114
|
+
'shift_right': (hid_code.KEY_SHIFT_R, False),
|
|
115
|
+
'alt': (hid_code.KEY_ALT_L, False),
|
|
116
|
+
'alt_left': (hid_code.KEY_ALT_L, False),
|
|
117
|
+
'alt_right': (hid_code.KEY_ALT_R, False),
|
|
118
|
+
'win': (hid_code.KEY_WIN_L, False),
|
|
119
|
+
'win_left': (hid_code.KEY_WIN_L, False),
|
|
120
|
+
'win_right': (hid_code.KEY_WIN_R, False),
|
|
121
|
+
'win_app': (hid_code.KEY_WIN_APP, False),
|
|
122
|
+
'gui': (hid_code.KEY_WIN_L, False),
|
|
123
|
+
'gui_left': (hid_code.KEY_WIN_L, False),
|
|
124
|
+
'gui_right': (hid_code.KEY_WIN_R, False),
|
|
125
|
+
# white spaces
|
|
126
|
+
' ': (hid_code.KEY_SPACE, False),
|
|
127
|
+
'\t': (hid_code.KEY_TAB, False),
|
|
128
|
+
'\n': (hid_code.KEY_ENTER, False),
|
|
129
|
+
# symbols
|
|
130
|
+
'-': (hid_code.KEY_MINUS, False),
|
|
131
|
+
'=': (hid_code.KEY_EQUAL, False),
|
|
132
|
+
'[': (hid_code.KEY_BRACKET_LEFT, False),
|
|
133
|
+
']': (hid_code.KEY_BRACKET_RIGHT, False),
|
|
134
|
+
'\\': (hid_code.KEY_CODE_29, False),
|
|
135
|
+
';': (hid_code.KEY_SEMICOLON, False),
|
|
136
|
+
'\'': (hid_code.KEY_APOSTROPHE, False),
|
|
137
|
+
'`': (hid_code.KEY_GRAVE, False),
|
|
138
|
+
',': (hid_code.KEY_COMMA, False),
|
|
139
|
+
'.': (hid_code.KEY_PERIOD, False),
|
|
140
|
+
'/': (hid_code.KEY_SLASH, False),
|
|
141
|
+
# symbols (shift)
|
|
142
|
+
'^': (hid_code.KEY_6, True),
|
|
143
|
+
'_': (hid_code.KEY_MINUS, True),
|
|
144
|
+
'{': (hid_code.KEY_BRACKET_LEFT, True),
|
|
145
|
+
'|': (hid_code.KEY_CODE_29, True),
|
|
146
|
+
'}': (hid_code.KEY_BRACKET_RIGHT, True),
|
|
147
|
+
'~': (hid_code.KEY_GRAVE, True),
|
|
148
|
+
'!': (hid_code.KEY_1, True),
|
|
149
|
+
'\"': (hid_code.KEY_APOSTROPHE, True),
|
|
150
|
+
'#': (hid_code.KEY_3, True),
|
|
151
|
+
'$': (hid_code.KEY_4, True),
|
|
152
|
+
'%': (hid_code.KEY_5, True),
|
|
153
|
+
'&': (hid_code.KEY_7, True),
|
|
154
|
+
'(': (hid_code.KEY_9, True),
|
|
155
|
+
')': (hid_code.KEY_0, True),
|
|
156
|
+
'*': (hid_code.KEY_8, True),
|
|
157
|
+
'+': (hid_code.KEY_EQUAL, True),
|
|
158
|
+
':': (hid_code.KEY_SEMICOLON, True),
|
|
159
|
+
'<': (hid_code.KEY_COMMA, True),
|
|
160
|
+
'>': (hid_code.KEY_PERIOD, True),
|
|
161
|
+
'?': (hid_code.KEY_SLASH, True),
|
|
162
|
+
'@': (hid_code.KEY_2, True),
|
|
163
|
+
# shift keys
|
|
164
|
+
'A': (hid_code.KEY_A, True),
|
|
165
|
+
'B': (hid_code.KEY_B, True),
|
|
166
|
+
'C': (hid_code.KEY_C, True),
|
|
167
|
+
'D': (hid_code.KEY_D, True),
|
|
168
|
+
'E': (hid_code.KEY_E, True),
|
|
169
|
+
'F': (hid_code.KEY_F, True),
|
|
170
|
+
'G': (hid_code.KEY_G, True),
|
|
171
|
+
'H': (hid_code.KEY_H, True),
|
|
172
|
+
'I': (hid_code.KEY_I, True),
|
|
173
|
+
'J': (hid_code.KEY_J, True),
|
|
174
|
+
'K': (hid_code.KEY_K, True),
|
|
175
|
+
'L': (hid_code.KEY_L, True),
|
|
176
|
+
'M': (hid_code.KEY_M, True),
|
|
177
|
+
'N': (hid_code.KEY_N, True),
|
|
178
|
+
'O': (hid_code.KEY_O, True),
|
|
179
|
+
'P': (hid_code.KEY_P, True),
|
|
180
|
+
'Q': (hid_code.KEY_Q, True),
|
|
181
|
+
'R': (hid_code.KEY_R, True),
|
|
182
|
+
'S': (hid_code.KEY_S, True),
|
|
183
|
+
'T': (hid_code.KEY_T, True),
|
|
184
|
+
'U': (hid_code.KEY_U, True),
|
|
185
|
+
'V': (hid_code.KEY_V, True),
|
|
186
|
+
'W': (hid_code.KEY_W, True),
|
|
187
|
+
'X': (hid_code.KEY_X, True),
|
|
188
|
+
'Y': (hid_code.KEY_Y, True),
|
|
189
|
+
'Z': (hid_code.KEY_Z, True),
|
|
190
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
KEY_GRAVE = b'\x35'
|
|
2
|
+
KEY_1 = b'\x1e'
|
|
3
|
+
KEY_2 = b'\x1f'
|
|
4
|
+
KEY_3 = b'\x20'
|
|
5
|
+
KEY_4 = b'\x21'
|
|
6
|
+
KEY_5 = b'\x22'
|
|
7
|
+
KEY_6 = b'\x23'
|
|
8
|
+
KEY_7 = b'\x24'
|
|
9
|
+
KEY_8 = b'\x25'
|
|
10
|
+
KEY_9 = b'\x26'
|
|
11
|
+
KEY_0 = b'\x27'
|
|
12
|
+
KEY_MINUS = b'\x2d'
|
|
13
|
+
KEY_EQUAL = b'\x2e'
|
|
14
|
+
KEY_CODE_14 = b'\x89'
|
|
15
|
+
KEY_BACKSPACE = b'\x2a'
|
|
16
|
+
KEY_TAB = b'\x2b'
|
|
17
|
+
KEY_Q = b'\x14'
|
|
18
|
+
KEY_W = b'\x1a'
|
|
19
|
+
KEY_E = b'\x08'
|
|
20
|
+
KEY_R = b'\x15'
|
|
21
|
+
KEY_T = b'\x17'
|
|
22
|
+
KEY_Y = b'\x1c'
|
|
23
|
+
KEY_U = b'\x18'
|
|
24
|
+
KEY_I = b'\x0c'
|
|
25
|
+
KEY_O = b'\x12'
|
|
26
|
+
KEY_P = b'\x13'
|
|
27
|
+
KEY_BRACKET_LEFT = b'\x2f'
|
|
28
|
+
KEY_BRACKET_RIGHT = b'\x30'
|
|
29
|
+
KEY_CODE_29 = b'\x31' # backslash
|
|
30
|
+
KEY_CAPS_LOCK = b'\x39'
|
|
31
|
+
KEY_A = b'\x04'
|
|
32
|
+
KEY_S = b'\x16'
|
|
33
|
+
KEY_D = b'\x07'
|
|
34
|
+
KEY_F = b'\x09'
|
|
35
|
+
KEY_G = b'\x0a'
|
|
36
|
+
KEY_H = b'\x0b'
|
|
37
|
+
KEY_J = b'\x0d'
|
|
38
|
+
KEY_K = b'\x0e'
|
|
39
|
+
KEY_L = b'\x0f'
|
|
40
|
+
KEY_SEMICOLON = b'\x33'
|
|
41
|
+
KEY_APOSTROPHE = b'\x34'
|
|
42
|
+
KEY_CODE_42 = b'\x32'
|
|
43
|
+
KEY_ENTER = b'\x28'
|
|
44
|
+
KEY_SHIFT_L = b'\xe1'
|
|
45
|
+
KEY_CODE_45 = b'\x64'
|
|
46
|
+
KEY_Z = b'\x1d'
|
|
47
|
+
KEY_X = b'\x1b'
|
|
48
|
+
KEY_C = b'\x06'
|
|
49
|
+
KEY_V = b'\x19'
|
|
50
|
+
KEY_B = b'\x05'
|
|
51
|
+
KEY_N = b'\x11'
|
|
52
|
+
KEY_M = b'\x10'
|
|
53
|
+
KEY_COMMA = b'\x36'
|
|
54
|
+
KEY_PERIOD = b'\x37'
|
|
55
|
+
KEY_SLASH = b'\x38'
|
|
56
|
+
KEY_CODE_56 = b'\x87' # Unknown
|
|
57
|
+
KEY_SHIFT_R = b'\xe5'
|
|
58
|
+
KEY_CTRL_L = b'\xe0'
|
|
59
|
+
#
|
|
60
|
+
KEY_ALT_L = b'\xe2'
|
|
61
|
+
KEY_SPACE = b'\x2c'
|
|
62
|
+
KEY_ALT_R = b'\xe6'
|
|
63
|
+
#
|
|
64
|
+
KEY_CTRL_R = b'\xe4'
|
|
65
|
+
#
|
|
66
|
+
#
|
|
67
|
+
#
|
|
68
|
+
#
|
|
69
|
+
#
|
|
70
|
+
#
|
|
71
|
+
#
|
|
72
|
+
#
|
|
73
|
+
#
|
|
74
|
+
#
|
|
75
|
+
KEY_INSERT = b'\x49'
|
|
76
|
+
KEY_DELETE = b'\x4c'
|
|
77
|
+
#
|
|
78
|
+
#
|
|
79
|
+
KEY_ARROW_LEFT = b'\x50'
|
|
80
|
+
KEY_HOME = b'\x4a'
|
|
81
|
+
KEY_END = b'\x4d'
|
|
82
|
+
#
|
|
83
|
+
KEY_ARROW_UP = b'\x52'
|
|
84
|
+
KEY_ARROW_DOWN = b'\x51'
|
|
85
|
+
KEY_PAGE_UP = b'\x4b'
|
|
86
|
+
KEY_PAGE_DOWN = b'\x4e'
|
|
87
|
+
#
|
|
88
|
+
#
|
|
89
|
+
KEY_ARROW_RIGHT = b'\x4f'
|
|
90
|
+
KEY_NUM_LOCK = b'\x53'
|
|
91
|
+
KEY_KEYPAD_7 = b'\x5f'
|
|
92
|
+
KEY_KEYPAD_4 = b'\x5c'
|
|
93
|
+
KEY_KEYPAD_1 = b'\x59'
|
|
94
|
+
#
|
|
95
|
+
KEY_KEYPAD_DIVIDE = b'\x54'
|
|
96
|
+
KEY_KEYPAD_8 = b'\x60'
|
|
97
|
+
KEY_KEYPAD_5 = b'\x5d'
|
|
98
|
+
KEY_KEYPAD_2 = b'\x5a'
|
|
99
|
+
KEY_KEYPAD_0 = b'\x62'
|
|
100
|
+
KEY_KEYPAD_MULTIPLY = b'\x55'
|
|
101
|
+
KEY_KEYPAD_9 = b'\x61'
|
|
102
|
+
KEY_KEYPAD_6 = b'\x5e'
|
|
103
|
+
KEY_KEYPAD_3 = b'\x5b'
|
|
104
|
+
KEY_KEYPAD_DECIMAL = b'\x63'
|
|
105
|
+
KEY_KEYPAD_SUBTRACT = b'\x56'
|
|
106
|
+
KEY_KEYPAD_ADD = b'\x57'
|
|
107
|
+
KEY_CODE_107 = b'\x85' # Unknown
|
|
108
|
+
KEY_KEYPAD_ENTER = b'\x58'
|
|
109
|
+
#
|
|
110
|
+
KEY_ESCAPE = b'\x29'
|
|
111
|
+
#
|
|
112
|
+
KEY_F1 = b'\x3a'
|
|
113
|
+
KEY_F2 = b'\x3b'
|
|
114
|
+
KEY_F3 = b'\x3c'
|
|
115
|
+
KEY_F4 = b'\x3d'
|
|
116
|
+
KEY_F5 = b'\x3e'
|
|
117
|
+
KEY_F6 = b'\x3f'
|
|
118
|
+
KEY_F7 = b'\x40'
|
|
119
|
+
KEY_F8 = b'\x41'
|
|
120
|
+
KEY_F9 = b'\x42'
|
|
121
|
+
KEY_F10 = b'\x43'
|
|
122
|
+
KEY_F11 = b'\x44'
|
|
123
|
+
KEY_F12 = b'\x45'
|
|
124
|
+
KEY_PRINT_SCREEN = b'\x46'
|
|
125
|
+
KEY_SCROLL_LOCK = b'\x47'
|
|
126
|
+
KEY_PAUSE = b'\x48'
|
|
127
|
+
#
|
|
128
|
+
#
|
|
129
|
+
#
|
|
130
|
+
#
|
|
131
|
+
#
|
|
132
|
+
#
|
|
133
|
+
#
|
|
134
|
+
#
|
|
135
|
+
#
|
|
136
|
+
#
|
|
137
|
+
#
|
|
138
|
+
#
|
|
139
|
+
#
|
|
140
|
+
#
|
|
141
|
+
#
|
|
142
|
+
#
|
|
143
|
+
#
|
|
144
|
+
#
|
|
145
|
+
#
|
|
146
|
+
#
|
|
147
|
+
# Japanese
|
|
148
|
+
# Japanese
|
|
149
|
+
# Japanese
|
|
150
|
+
# KoreaKC-L
|
|
151
|
+
# KoreaKC-R
|
|
152
|
+
# ACPI
|
|
153
|
+
# ACPI
|
|
154
|
+
# ACPI
|
|
155
|
+
KEY_WIN_L = b'\xe3'
|
|
156
|
+
KEY_WIN_R = b'\xe7'
|
|
157
|
+
KEY_WIN_APP = b'\x65'
|
|
158
|
+
KEY_NONE = b'\x00'
|
|
159
|
+
|
|
160
|
+
# Reference: CH9329 chip document page 15 'CH9329 Key Code Table'
|
|
161
|
+
# Line number denotes the serial number in the documentation
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import time
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
from typing import Dict
|
|
5
|
+
from typing import List
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from typing import Tuple
|
|
8
|
+
|
|
9
|
+
from serial import Serial
|
|
10
|
+
|
|
11
|
+
import pych9329.frame_utils as frame_utils
|
|
12
|
+
from pych9329.hid_code_map import HID_CODE_MAP
|
|
13
|
+
|
|
14
|
+
MODIFIER_KEY_NAME_MAP = {
|
|
15
|
+
"ctrl": 0b00000001,
|
|
16
|
+
"ctrl_left": 0b00000001,
|
|
17
|
+
"shift": 0b00000010,
|
|
18
|
+
"shift_left": 0b00000010,
|
|
19
|
+
"alt": 0b00000100,
|
|
20
|
+
"alt_left": 0b00000100,
|
|
21
|
+
"win": 0b00001000,
|
|
22
|
+
"win_left": 0b00001000,
|
|
23
|
+
"ctrl_right": 0b00010000,
|
|
24
|
+
"shift_right": 0b00100000,
|
|
25
|
+
"alt_right": 0b01000000,
|
|
26
|
+
"win_right": 0b10000000,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def deduplicate_list(lt: List) -> List:
|
|
31
|
+
return list(OrderedDict.fromkeys(lt))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def receive_indicator_status(serial_object: Serial) -> Tuple[bool, Dict[str, bool]]:
|
|
35
|
+
result_dict: Dict[str, bool] = {
|
|
36
|
+
"usb_connect_status": False,
|
|
37
|
+
"num_lock": False,
|
|
38
|
+
"caps_lock": False,
|
|
39
|
+
"scroll_lock": False,
|
|
40
|
+
}
|
|
41
|
+
result_value = False
|
|
42
|
+
data_frame = frame_utils.DataFrame(
|
|
43
|
+
b"\x57\xab",
|
|
44
|
+
b"\x00",
|
|
45
|
+
b"\x01",
|
|
46
|
+
b"\x00",
|
|
47
|
+
b"",
|
|
48
|
+
)
|
|
49
|
+
frame_packet = data_frame.create_frame()
|
|
50
|
+
# clear connection buffer
|
|
51
|
+
serial_object.readall()
|
|
52
|
+
# write command
|
|
53
|
+
serial_object.write(frame_packet)
|
|
54
|
+
serial_object.flush()
|
|
55
|
+
# read packet
|
|
56
|
+
reply_packet: bytes = serial_object.readall()
|
|
57
|
+
parse_result = data_frame.parse_frame(reply_packet)
|
|
58
|
+
if parse_result is False:
|
|
59
|
+
return result_value, result_dict
|
|
60
|
+
reply_data = data_frame.get_data()
|
|
61
|
+
# 获取USB连接状态
|
|
62
|
+
usb_connect_status: bytes = reply_data[1:2]
|
|
63
|
+
if usb_connect_status == b"0x00":
|
|
64
|
+
result_dict["usb_connect_status"] = False
|
|
65
|
+
else:
|
|
66
|
+
result_dict["usb_connect_status"] = True
|
|
67
|
+
# 获取键盘锁定状态
|
|
68
|
+
indicator_bytes: bytes = reply_data[2:3]
|
|
69
|
+
indicator_flag = int.from_bytes(indicator_bytes, byteorder="little")
|
|
70
|
+
if indicator_flag & 0x01 != 0:
|
|
71
|
+
result_dict["num_lock"] = True
|
|
72
|
+
else:
|
|
73
|
+
result_dict["num_lock"] = False
|
|
74
|
+
if indicator_flag & 0x02 != 0:
|
|
75
|
+
result_dict["caps_lock"] = True
|
|
76
|
+
else:
|
|
77
|
+
result_dict["caps_lock"] = False
|
|
78
|
+
if indicator_flag & 0x04 != 0:
|
|
79
|
+
result_dict["scroll_lock"] = True
|
|
80
|
+
else:
|
|
81
|
+
result_dict["scroll_lock"] = False
|
|
82
|
+
result_value = True
|
|
83
|
+
return result_value, result_dict
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def send_general_data(
|
|
87
|
+
serial_object: Serial,
|
|
88
|
+
key_tuple: Tuple[str, str, str, str, str, str] = ("", "", "", "", "", ""),
|
|
89
|
+
modifiers: Optional[List[str]] = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
if modifiers is None:
|
|
92
|
+
modifiers = []
|
|
93
|
+
|
|
94
|
+
# CMD_SEND_KB_GENERAL_DATA data has exactly 8 bytes
|
|
95
|
+
data = b""
|
|
96
|
+
|
|
97
|
+
# first byte modifiers key, each bit represents 1 key
|
|
98
|
+
#
|
|
99
|
+
# BIT0 - ctrl_left
|
|
100
|
+
# BIT1 - shift_left
|
|
101
|
+
# BIT2 - alt_left
|
|
102
|
+
# BIT3 - win_left
|
|
103
|
+
# BIT4 - ctrl_right
|
|
104
|
+
# BIT5 - shift_right
|
|
105
|
+
# BIT6 - alt_right
|
|
106
|
+
# BIT7 - win_right
|
|
107
|
+
modifier_value: int = 0x00
|
|
108
|
+
for key_name in modifiers:
|
|
109
|
+
if key_name not in MODIFIER_KEY_NAME_MAP:
|
|
110
|
+
raise InvalidModifierKey(key_name)
|
|
111
|
+
modifier_value |= MODIFIER_KEY_NAME_MAP[key_name]
|
|
112
|
+
data += modifier_value.to_bytes(1, byteorder="little")
|
|
113
|
+
|
|
114
|
+
# second byte must be 0x00
|
|
115
|
+
data += b"\x00"
|
|
116
|
+
|
|
117
|
+
# third to eighth bytes are keys
|
|
118
|
+
# we can press upto 6 buttons
|
|
119
|
+
for key in key_tuple:
|
|
120
|
+
if key not in HID_CODE_MAP:
|
|
121
|
+
raise InvalidKey(key)
|
|
122
|
+
hid_code, _ = HID_CODE_MAP[key]
|
|
123
|
+
data += hid_code
|
|
124
|
+
|
|
125
|
+
# create packet and send
|
|
126
|
+
data_frame = frame_utils.DataFrame(
|
|
127
|
+
b"\x57\xab",
|
|
128
|
+
b"\x00",
|
|
129
|
+
b"\x02",
|
|
130
|
+
b"",
|
|
131
|
+
b"",
|
|
132
|
+
)
|
|
133
|
+
data_frame.set_data(data)
|
|
134
|
+
frame_packet = data_frame.create_frame()
|
|
135
|
+
serial_object.write(frame_packet)
|
|
136
|
+
serial_object.flush()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def trigger(
|
|
140
|
+
serial_object: Serial,
|
|
141
|
+
keys: list[str],
|
|
142
|
+
modifiers: Optional[List[str]] = None,
|
|
143
|
+
) -> None:
|
|
144
|
+
press_keys = keys.copy()
|
|
145
|
+
press_modifiers = modifiers.copy()
|
|
146
|
+
press_keys = deduplicate_list(press_keys)
|
|
147
|
+
press_modifiers = deduplicate_list(press_modifiers)
|
|
148
|
+
# Supports press to 6 normal buttons at the same time
|
|
149
|
+
if len(press_keys) > 6:
|
|
150
|
+
raise TooManyKeys("CH9329 supports maximum of 6 keys to be pressed at once.")
|
|
151
|
+
if len(modifiers) > 8:
|
|
152
|
+
raise TooManyKeys(
|
|
153
|
+
"CH9329 supports maximum of 8 control keys to be pressed at once."
|
|
154
|
+
)
|
|
155
|
+
# if len(keys) <= 6, add empty keys
|
|
156
|
+
while len(press_keys) != 6:
|
|
157
|
+
press_keys.append("")
|
|
158
|
+
send_general_data(
|
|
159
|
+
serial_object,
|
|
160
|
+
(
|
|
161
|
+
press_keys[0],
|
|
162
|
+
press_keys[1],
|
|
163
|
+
press_keys[2],
|
|
164
|
+
press_keys[3],
|
|
165
|
+
press_keys[4],
|
|
166
|
+
press_keys[5],
|
|
167
|
+
),
|
|
168
|
+
press_modifiers,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def press(
|
|
173
|
+
serial_object: Serial, key: str, modifiers: Optional[List[str]] = None
|
|
174
|
+
) -> None:
|
|
175
|
+
if modifiers is None:
|
|
176
|
+
modifiers_copy = []
|
|
177
|
+
elif isinstance(modifiers, list):
|
|
178
|
+
modifiers_copy = modifiers.copy()
|
|
179
|
+
else:
|
|
180
|
+
modifiers_copy = []
|
|
181
|
+
if key not in HID_CODE_MAP:
|
|
182
|
+
raise InvalidKey(key)
|
|
183
|
+
_, shift = HID_CODE_MAP[key]
|
|
184
|
+
if shift:
|
|
185
|
+
modifiers_copy.append("shift")
|
|
186
|
+
send_general_data(serial_object, (key, "", "", "", "", ""), modifiers_copy)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def release(serial_object: Serial) -> None:
|
|
190
|
+
send_general_data(serial_object, ("", "", "", "", "", ""))
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def click(
|
|
194
|
+
serial_object: Serial,
|
|
195
|
+
key: str,
|
|
196
|
+
min_interval: float = 0.02,
|
|
197
|
+
max_interval: float = 0.05,
|
|
198
|
+
) -> None:
|
|
199
|
+
sleep_time = random.uniform(min_interval, max_interval)
|
|
200
|
+
press(serial_object, key)
|
|
201
|
+
time.sleep(sleep_time)
|
|
202
|
+
release(serial_object)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def send_text_data(
|
|
206
|
+
ser: Serial,
|
|
207
|
+
text: str,
|
|
208
|
+
min_interval: float = 0.02,
|
|
209
|
+
max_interval: float = 0.05,
|
|
210
|
+
) -> None:
|
|
211
|
+
for char in text:
|
|
212
|
+
click(ser, char, min_interval, max_interval)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
if __name__ == "__main__":
|
|
216
|
+
pass
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import time
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from serial import Serial
|
|
6
|
+
|
|
7
|
+
import pych9329.exceptions as exceptions
|
|
8
|
+
import pych9329.frame_utils as frame_utils
|
|
9
|
+
|
|
10
|
+
MOUSE_BUTTON_NAME_MAP: typing.Final[dict[str, bytes]] = {
|
|
11
|
+
'null': b'\x00',
|
|
12
|
+
'left': b'\x01',
|
|
13
|
+
'right': b'\x02',
|
|
14
|
+
'center': b'\x04',
|
|
15
|
+
# alias
|
|
16
|
+
'none': b'\x00',
|
|
17
|
+
'middle': b'\x04',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def check_integer_range(value: int, minimum: int, maximum: int) -> bool:
|
|
22
|
+
if value < minimum or value > maximum:
|
|
23
|
+
return False
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def send_absolute_data(
|
|
28
|
+
serial_object: Serial,
|
|
29
|
+
x: int,
|
|
30
|
+
y: int,
|
|
31
|
+
button_name: str = 'null',
|
|
32
|
+
x_max: int = 1920,
|
|
33
|
+
y_max: int = 1080,
|
|
34
|
+
wheel_value: int = 0,
|
|
35
|
+
) -> None:
|
|
36
|
+
if not check_integer_range(x_max, 0, 4096):
|
|
37
|
+
raise exceptions.InvalidCoordinateValue(
|
|
38
|
+
'Max coordinate value should be between 0 and 4096'
|
|
39
|
+
)
|
|
40
|
+
if not check_integer_range(y_max, 0, 4096):
|
|
41
|
+
raise exceptions.InvalidCoordinateValue(
|
|
42
|
+
'Max coordinate value should be between 0 and 4096'
|
|
43
|
+
)
|
|
44
|
+
data_frame = frame_utils.DataFrame(
|
|
45
|
+
b'\x57\xab',
|
|
46
|
+
b'\x00',
|
|
47
|
+
b'\x04',
|
|
48
|
+
b'',
|
|
49
|
+
b'',
|
|
50
|
+
)
|
|
51
|
+
# CMD_SEND_MS_ABS_DATA requires 7 bytes data
|
|
52
|
+
# first byte is always 0x02
|
|
53
|
+
buffer = b'\x02'
|
|
54
|
+
|
|
55
|
+
# second byte is mouse button value
|
|
56
|
+
buffer += MOUSE_BUTTON_NAME_MAP[button_name]
|
|
57
|
+
|
|
58
|
+
# third and fourth bytes are x-coordinates
|
|
59
|
+
x_value = (4096 * x) // x_max
|
|
60
|
+
buffer += x_value.to_bytes(2, byteorder='little')
|
|
61
|
+
|
|
62
|
+
# fifth and sixth bytes are y-coordinates
|
|
63
|
+
y_value = (4096 * y) // y_max
|
|
64
|
+
buffer += y_value.to_bytes(2, byteorder='little')
|
|
65
|
+
|
|
66
|
+
# seventh byte contains wheel data
|
|
67
|
+
# If it is 0x00, it means there is no scrolling action
|
|
68
|
+
# 0x01-0x7F, means scroll up
|
|
69
|
+
# 0x81-0xFF, means scroll down
|
|
70
|
+
if not check_integer_range(wheel_value, -128, 127):
|
|
71
|
+
raise exceptions.InvalidWheelValue(
|
|
72
|
+
'Wheel value should be between -127 and 128'
|
|
73
|
+
)
|
|
74
|
+
buffer += wheel_value.to_bytes(1, byteorder='little', signed=True)
|
|
75
|
+
data_frame.set_data(buffer)
|
|
76
|
+
request_packet = data_frame.create_frame()
|
|
77
|
+
serial_object.write(request_packet)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def send_relative_data(
|
|
81
|
+
serial_object: Serial,
|
|
82
|
+
x: int,
|
|
83
|
+
y: int,
|
|
84
|
+
button_name: str = 'null',
|
|
85
|
+
wheel_value: int = 0,
|
|
86
|
+
) -> None:
|
|
87
|
+
data_frame = frame_utils.DataFrame(
|
|
88
|
+
b'\x57\xab',
|
|
89
|
+
b'\x00',
|
|
90
|
+
b'\x05',
|
|
91
|
+
b'',
|
|
92
|
+
b'',
|
|
93
|
+
)
|
|
94
|
+
# CMD_SEND_MS_REL_DATA requires 5 bytes data
|
|
95
|
+
# first byte is always 0x01
|
|
96
|
+
buffer = b'\x01'
|
|
97
|
+
buffer += MOUSE_BUTTON_NAME_MAP[button_name]
|
|
98
|
+
|
|
99
|
+
# x value
|
|
100
|
+
if not check_integer_range(x, -128, 127):
|
|
101
|
+
raise exceptions.InvalidCoordinateValue(
|
|
102
|
+
'Coordinate value should be between -127 and 128'
|
|
103
|
+
)
|
|
104
|
+
buffer += x.to_bytes(1, byteorder='little', signed=True)
|
|
105
|
+
# y value
|
|
106
|
+
if not check_integer_range(y, -128, 127):
|
|
107
|
+
raise exceptions.InvalidCoordinateValue(
|
|
108
|
+
'Coordinate value should be between -127 and 128'
|
|
109
|
+
)
|
|
110
|
+
buffer += y.to_bytes(1, byteorder='little', signed=True)
|
|
111
|
+
|
|
112
|
+
# wheel value
|
|
113
|
+
if not check_integer_range(wheel_value, -128, 127):
|
|
114
|
+
raise exceptions.InvalidWheelValue(
|
|
115
|
+
'Wheel value should be between -127 and 128'
|
|
116
|
+
)
|
|
117
|
+
buffer += wheel_value.to_bytes(1, byteorder='little', signed=True)
|
|
118
|
+
|
|
119
|
+
data_frame.set_data(buffer)
|
|
120
|
+
request_packet = data_frame.create_frame()
|
|
121
|
+
serial_object.write(request_packet)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def move(
|
|
125
|
+
serial_object: Serial,
|
|
126
|
+
x: int,
|
|
127
|
+
y: int,
|
|
128
|
+
relative_mode: bool = False,
|
|
129
|
+
monitor_width: int = 1920,
|
|
130
|
+
monitor_height: int = 1080,
|
|
131
|
+
) -> None:
|
|
132
|
+
if relative_mode:
|
|
133
|
+
send_relative_data(serial_object, x, y, 'null')
|
|
134
|
+
else:
|
|
135
|
+
send_absolute_data(serial_object, x, y, 'null', monitor_width, monitor_height)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def press(serial_object: Serial, button_name: str = 'left') -> None:
|
|
139
|
+
send_relative_data(serial_object, 0, 0, button_name, 0)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def release(serial_object: Serial) -> None:
|
|
143
|
+
send_relative_data(serial_object, 0, 0, 'null', 0)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def click(serial_object: Serial, button_name: str = 'left') -> None:
|
|
147
|
+
press(serial_object, button_name)
|
|
148
|
+
# 100 to 400 milliseconds delay for simulating natural behavior
|
|
149
|
+
sleep_time = random.uniform(0.20, 0.40)
|
|
150
|
+
time.sleep(sleep_time)
|
|
151
|
+
release(serial_object)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def wheel(serial_object: Serial, wheel_value: int = 1) -> None:
|
|
155
|
+
send_relative_data(serial_object, 0, 0, 'null', wheel_value=wheel_value)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == '__main__':
|
|
159
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
import typing
|
|
3
|
+
import pych9329.exceptions as exceptions
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StructureMeta(type):
|
|
8
|
+
def __init__(
|
|
9
|
+
cls, _cls_name: str, _bases: tuple[type, ...], _cls_dict: dict[str, Any]
|
|
10
|
+
):
|
|
11
|
+
super().__init__(cls)
|
|
12
|
+
fields = getattr(cls, '_fields_', [])
|
|
13
|
+
|
|
14
|
+
offset = 0
|
|
15
|
+
for field_format, field_name in fields:
|
|
16
|
+
if field_format.startswith(('<', '>', '!', '@')):
|
|
17
|
+
byte_order = field_format[0]
|
|
18
|
+
field_format = field_format[1:]
|
|
19
|
+
else:
|
|
20
|
+
byte_order = ''
|
|
21
|
+
field_format = byte_order + field_format
|
|
22
|
+
offset += struct.calcsize(field_format)
|
|
23
|
+
setattr(cls, '_fields_struct_size_', offset)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Structure(metaclass=StructureMeta):
|
|
27
|
+
def __init__(self, data: bytes):
|
|
28
|
+
self.buffer = data
|
|
29
|
+
self.attribute = dict()
|
|
30
|
+
self.load()
|
|
31
|
+
|
|
32
|
+
def load(self):
|
|
33
|
+
fields = self._fields_
|
|
34
|
+
offset = 0
|
|
35
|
+
for field_format, field_name in fields:
|
|
36
|
+
field_length = struct.calcsize(field_format)
|
|
37
|
+
field_data = struct.unpack_from(field_format, self.buffer, offset)
|
|
38
|
+
if len(field_data) == 1:
|
|
39
|
+
self.attribute[field_name] = field_data[0]
|
|
40
|
+
elif len(field_data) == field_length:
|
|
41
|
+
buffer = bytearray()
|
|
42
|
+
for item in field_data:
|
|
43
|
+
buffer += item
|
|
44
|
+
self.attribute[field_name] = bytes(buffer)
|
|
45
|
+
else:
|
|
46
|
+
self.attribute[field_name] = None
|
|
47
|
+
offset += struct.calcsize(field_format)
|
|
48
|
+
|
|
49
|
+
def save(self):
|
|
50
|
+
fields = self._fields_
|
|
51
|
+
binary_data = bytearray()
|
|
52
|
+
for field_format, field_name in fields:
|
|
53
|
+
field_data = self.attribute[field_name]
|
|
54
|
+
field_bytes = struct.pack(field_format, field_data)
|
|
55
|
+
binary_data += bytearray(field_bytes)
|
|
56
|
+
self.buffer = bytes(binary_data)
|
|
57
|
+
|
|
58
|
+
def get(self, key: str) -> Any:
|
|
59
|
+
data = self.attribute.get(key, None)
|
|
60
|
+
return data
|
|
61
|
+
|
|
62
|
+
def set(self, key: str, value: Any) -> None:
|
|
63
|
+
self.attribute[key] = value
|
|
64
|
+
|
|
65
|
+
def buffer_load(self, buffer: bytes) -> None:
|
|
66
|
+
self.buffer = buffer
|
|
67
|
+
self.attribute.clear()
|
|
68
|
+
self.load()
|
|
69
|
+
|
|
70
|
+
def buffer_flush(self) -> None:
|
|
71
|
+
self.save()
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_buffer(cls, buffer: bytes):
|
|
75
|
+
buffer_len = len(buffer)
|
|
76
|
+
if buffer_len >= cls._fields_struct_size_:
|
|
77
|
+
return cls(buffer)
|
|
78
|
+
else:
|
|
79
|
+
raise exceptions.BufferTooSmall()
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_file(cls, buffer: typing.BinaryIO):
|
|
83
|
+
return cls(buffer.read(cls._fields_struct_size_))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
'''
|
|
87
|
+
class ExampleStruct(Structure):
|
|
88
|
+
_fields_ = [
|
|
89
|
+
('d', 'min_x'),
|
|
90
|
+
('d', 'min_y'),
|
|
91
|
+
]
|
|
92
|
+
'''
|
|
93
|
+
|
|
94
|
+
if __name__ == '__main__':
|
|
95
|
+
pass
|