mxdbg 0.2.2__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.
mxdbg.toml ADDED
@@ -0,0 +1,197 @@
1
+ [version]
2
+ MAJOR = 1
3
+ MINOR = 0
4
+
5
+ [task_cmd]
6
+ TASK_IDLE = 0
7
+ TASK_I2C_WRITE_READ = 1
8
+ TASK_I2C_CONFIG = 2
9
+ TASK_GPIO_WRITE_READ = 3
10
+ TASK_GPIO_CONFIG = 4
11
+ TASK_SPI_WRITE_READ = 5
12
+ TASK_SPI_CONFIG = 6
13
+ TASK_PWM_RUN_STOP = 7
14
+ TASK_PWM_CONFIG = 8
15
+ TASK_SPI_READ_IMAGE = 9
16
+ TASK_USB_CONFIG = 240
17
+ TASK_RESET_DEVICE = 241
18
+
19
+ [error_map.ESPRESSIF_OK]
20
+ code = 0
21
+ desc = "esp_err_t value indicating success (no error)"
22
+
23
+ [error_map.ESPRESSIF_FAIL]
24
+ code = -1
25
+ desc = "Generic esp_err_t code indicating failure"
26
+
27
+ [error_map.ESPRESSIF_ERR_NO_MEM]
28
+ code = 257
29
+ desc = "Out of memory"
30
+
31
+ [error_map.ESPRESSIF_ERR_INVALID_ARG]
32
+ code = 258
33
+ desc = "Invalid argument"
34
+
35
+ [error_map.ESPRESSIF_ERR_INVALID_STATE]
36
+ code = 259
37
+ desc = "Invalid state"
38
+
39
+ [error_map.ESPRESSIF_ERR_INVALID_SIZE]
40
+ code = 260
41
+ desc = "Invalid size"
42
+
43
+ [error_map.ESPRESSIF_ERR_NOT_FOUND]
44
+ code = 261
45
+ desc = "Requested resource not found"
46
+
47
+ [error_map.ESPRESSIF_ERR_NOT_SUPPORTED]
48
+ code = 262
49
+ desc = "Operation or feature not supported"
50
+
51
+ [error_map.ESPRESSIF_ERR_TIMEOUT]
52
+ code = 263
53
+ desc = "Operation timed out"
54
+
55
+ [error_map.ESPRESSIF_ERR_INVALID_RESPONSE]
56
+ code = 264
57
+ desc = "Received response was invalid"
58
+
59
+ [error_map.ESPRESSIF_ERR_INVALID_CRC]
60
+ code = 265
61
+ desc = "CRC or checksum was invalid"
62
+
63
+ [error_map.ESPRESSIF_ERR_INVALID_VERSION]
64
+ code = 266
65
+ desc = "Version was invalid"
66
+
67
+ [error_map.ESPRESSIF_ERR_INVALID_MAC]
68
+ code = 267
69
+ desc = "MAC address was invalid"
70
+
71
+ [error_map.ESPRESSIF_ERR_NOT_FINISHED]
72
+ code = 268
73
+ desc = "There are items remained to retrieve"
74
+
75
+ [error_map.ESPRESSIF_ERR_WIFI_BASE]
76
+ code = 12288
77
+ desc = "Starting number of WiFi error codes"
78
+
79
+ [error_map.ESPRESSIF_ERR_MESH_BASE]
80
+ code = 16384
81
+ desc = "Starting number of MESH error codes"
82
+
83
+ [error_map.ESPRESSIF_ERR_FLASH_BASE]
84
+ code = 24576
85
+ desc = "Starting number of flash error codes"
86
+
87
+ [error_map.ESPRESSIF_ERR_HW_CRYPTO_BASE]
88
+ code = 49152
89
+ desc = "Starting number of HW cryptography module error codes"
90
+
91
+ [error_map.ESPRESSIF_ERR_MEMPROT_BASE]
92
+ code = 53248
93
+ desc = "Starting number of Memory Protection API error codes"
94
+
95
+ [error_map.MXDBG_ERR_UNKNOWN_TASK]
96
+ code = 16842496
97
+ desc = "Unknown task"
98
+
99
+ [error_map.MXDBG_ERR_DATA_UNPACK_FAILED]
100
+ code = 16834560
101
+ desc = "Data unpack failed"
102
+
103
+ [error_map.MXDBG_ERR_DATA_UNPACK_HEADER_ERROR]
104
+ code = 16834561
105
+ desc = "Data unpack header error"
106
+
107
+ [error_map.MXDBG_ERR_DATA_UNPACK_SEPARATOR_ERROR]
108
+ code = 16834562
109
+ desc = "Data unpack separator error"
110
+
111
+ [error_map.MXDBG_ERR_USB_RX_DATA_FAILED]
112
+ code = 16834816
113
+ desc = "USB RX data failed"
114
+
115
+ [error_map.MXDBG_ERR_USB_RX_DATA_TIMEOUT]
116
+ code = 16834818
117
+ desc = "USB RX data timeout"
118
+
119
+ [error_map.MXDBG_ERR_USB_RX_DATA_HEADER_ERROR]
120
+ code = 16834819
121
+ desc = "USB RX data header error"
122
+
123
+ [error_map.MXDBG_ERR_PWM_RUN_ALREADY]
124
+ code = 16779009
125
+ desc = "PWM is running already."
126
+
127
+ [error_map.MXDBG_ERR_PWM_STOP_ALREADY]
128
+ code = 16779010
129
+ desc = "PWM is stopped already."
130
+
131
+ [error_map.MXDBG_ERR_PWM_CONFIG_FAILED]
132
+ code = 16779264
133
+ desc = "PWM config failed"
134
+
135
+ [error_map.MXDBG_ERR_PWM_INVALID_CHANNEL]
136
+ code = 16779265
137
+ desc = "Invalid PWM channel"
138
+
139
+ [error_map.MXDBG_ERR_PWM_INVALID_PARAMS]
140
+ code = 16779266
141
+ desc = "Invalid PWM parameters"
142
+
143
+ [error_map.MXDBG_ERR_PWM_INVALID_DUTY_CYCLES]
144
+ code = 16779267
145
+ desc = "Invalid PWM duty cycles"
146
+
147
+ [error_map.MXDBG_ERR_SPI_READ_IMAGE_FAILED]
148
+ code = 16779520
149
+ desc = "SPI read image failed"
150
+
151
+ [error_map.MXDBG_ERR_I2C_INIT_FAILED]
152
+ code = 16777728
153
+ desc = "I2C init failed"
154
+
155
+ [error_map.MXDBG_ERR_I2C_DEINIT_FAILED]
156
+ code = 16777729
157
+ desc = "I2C deinit failed"
158
+
159
+ [error_map.MXDBG_ERR_I2C_INVALID_OPERATION]
160
+ code = 16777474
161
+ desc = "Invalid I2C operation"
162
+
163
+ [error_map.MXDBG_ERR_I2C_WRITE_READ_FAILED]
164
+ code = 16777475
165
+ desc = "I2C write read failed"
166
+
167
+ [error_map.MXDBG_ERR_I2C_WRITE_FAILED]
168
+ code = 16777476
169
+ desc = "I2C write failed"
170
+
171
+ [error_map.MXDBG_ERR_I2C_READ_FAILED]
172
+ code = 16777477
173
+ desc = "I2C read failed"
174
+
175
+ [error_map.MXDBG_ERR_SPI_INIT_FAILED]
176
+ code = 16778752
177
+ desc = "SPI init failed"
178
+
179
+ [error_map.MXDBG_ERR_SPI_DEINIT_FAILED]
180
+ code = 16778753
181
+ desc = "SPI deinit failed"
182
+
183
+ [error_map.MXDBG_ERR_SPI_INVALID_OPERATION]
184
+ code = 16778498
185
+ desc = "Invalid SPI operation"
186
+
187
+ [error_map.MXDBG_ERR_SPI_READ_LEN_TOO_LONG]
188
+ code = 16778499
189
+ desc = "SPI read length too long"
190
+
191
+ [error_map.MXDBG_ERR_SPI_TRANSACTION_FAILED]
192
+ code = 16778500
193
+ desc = "SPI transaction failed"
194
+
195
+ [error_map.MXDBG_ERR_GPIO_CONFIG_FAILED]
196
+ code = 16778240
197
+ desc = "GPIO config failed"
mxdbg_ui.py ADDED
@@ -0,0 +1,262 @@
1
+ import inspect
2
+ import io
3
+ import sys
4
+
5
+ import streamlit as st
6
+ from mxdbg import MXDBG
7
+
8
+ # 页面配置必须在顶部
9
+ st.set_page_config(page_title="MXDBG Web UI", layout="wide")
10
+
11
+
12
+ # 定义全局 MXDBG 对象
13
+ @st.cache_resource
14
+ def get_mxdbg():
15
+ """创建并返回 MXDBG 对象(单例模式)"""
16
+ try:
17
+ mxdbg_instance = MXDBG()
18
+ if not hasattr(mxdbg_instance, "_client"): # 确保 _client 属性存在
19
+ mxdbg_instance._client = None
20
+ return mxdbg_instance
21
+ except ValueError as e:
22
+ st.error(f"无法连接设备:{e}")
23
+ return None
24
+
25
+
26
+ # 初始化 MXDBG 对象
27
+ mxdbg = get_mxdbg()
28
+
29
+ # 定义分类规则
30
+ categories = {
31
+ "GPIO": ["gpio_"],
32
+ "PWM": ["pwm_"],
33
+ "SPI": ["spi_"],
34
+ "I2C": ["i2c_"],
35
+ "POWER": ["power_"],
36
+ "EXPAND IO": ["expand_io_"],
37
+ "OTHERS": [],
38
+ }
39
+
40
+
41
+ # 动态提取和分类接口
42
+ def extract_and_categorize_methods(mxdbg):
43
+ """从 MXDBG 类中动态提取方法并分类"""
44
+ if not mxdbg:
45
+ return {key: [] for key in categories}
46
+
47
+ # 提取所有方法(过滤掉双下划线开头、私有方法和非方法的属性)
48
+ methods = [
49
+ method
50
+ for method in dir(mxdbg)
51
+ if callable(getattr(mxdbg, method))
52
+ and not method.startswith("__")
53
+ and not method.startswith("_")
54
+ ]
55
+
56
+ categorized_methods = {key: [] for key in categories}
57
+
58
+ for method in methods:
59
+ categorized = False
60
+ for category, prefixes in categories.items():
61
+ if any(method.lower().startswith(prefix) for prefix in prefixes):
62
+ categorized_methods[category].append(method)
63
+ categorized = True
64
+ break
65
+ if not categorized:
66
+ categorized_methods["OTHERS"].append(method)
67
+
68
+ return categorized_methods
69
+
70
+
71
+ # 获取接口的签名和默认参数
72
+ def get_method_signature(obj, method_name):
73
+ """获取方法签名和默认参数"""
74
+ method = getattr(obj, method_name, None)
75
+ if not method:
76
+ return f"{method_name}()"
77
+ try:
78
+ sig = inspect.signature(method)
79
+ params = [
80
+ f"{name}={param.default!r}" if param.default is not param.empty else name
81
+ for name, param in sig.parameters.items()
82
+ ]
83
+ return f"{method_name}({', '.join(params)})"
84
+ except Exception:
85
+ return f"{method_name}()"
86
+
87
+
88
+ # 获取分类后的方法
89
+ categorized_methods = extract_and_categorize_methods(mxdbg)
90
+
91
+ # 页面布局
92
+ st.title("MXDBG Web UI")
93
+
94
+ col1, col2 = st.columns([1, 2])
95
+
96
+ # 左侧功能选项
97
+ with col1:
98
+ st.subheader("功能选项卡")
99
+ selected_tab = st.radio(
100
+ "选择功能类别", list(categorized_methods.keys()), horizontal=True
101
+ )
102
+
103
+ st.write(f"### {selected_tab} 的接口列表")
104
+ matching_interfaces = categorized_methods.get(selected_tab, [])
105
+ for interface in matching_interfaces:
106
+ if st.button(f"选择接口:{interface}", key=f"select_{interface}"):
107
+ # 自动填写接口默认参数
108
+ method_call = get_method_signature(mxdbg, interface)
109
+ if "program_box" not in st.session_state:
110
+ st.session_state["program_box"] = []
111
+ st.session_state["cursor_position"] = len(
112
+ "\n".join(st.session_state["program_box"])
113
+ ) # 初始化光标位置
114
+
115
+ # 插入到光标位置
116
+ cursor_pos = st.session_state.get(
117
+ "cursor_position", len("\n".join(st.session_state["program_box"]))
118
+ )
119
+ current_code = "\n".join(st.session_state["program_box"])
120
+ new_code = (
121
+ current_code[:cursor_pos]
122
+ + f"\nmxdbg.{method_call}\n"
123
+ + current_code[cursor_pos:]
124
+ )
125
+
126
+ # 更新程序框内容
127
+ st.session_state["program_box"] = new_code.split("\n")
128
+
129
+ # 右侧程序框和日志框
130
+ with col2:
131
+ st.subheader("程序框")
132
+ if "program_box" not in st.session_state:
133
+ st.session_state["program_box"] = []
134
+ st.session_state["cursor_position"] = 0
135
+
136
+ # 显示程序框内容
137
+ program_code = "\n".join(st.session_state["program_box"])
138
+ new_code = st.text_area(
139
+ "程序框",
140
+ program_code,
141
+ height=300,
142
+ key="program_box_text_area",
143
+ on_change=lambda: st.session_state.update(
144
+ {"cursor_position": len(st.session_state["program_box_text_area"])}
145
+ ),
146
+ )
147
+
148
+ # 更新程序框内容
149
+ if new_code != program_code:
150
+ st.session_state["program_box"] = new_code.split("\n")
151
+
152
+ # 删除选定的接口
153
+ if st.button("清空程序框"):
154
+ st.session_state["program_box"] = []
155
+
156
+ # 执行按钮
157
+ st.subheader("执行代码")
158
+ result_output = st.empty() # 显示执行结果
159
+ if st.button("执行程序框中的代码"):
160
+ if mxdbg:
161
+ try:
162
+ # 重定向 stdout 捕获执行结果
163
+ old_stdout = sys.stdout
164
+ redirected_output = io.StringIO()
165
+ sys.stdout = redirected_output
166
+
167
+ # 执行代码(仅允许公开 API 调用)
168
+ import ast
169
+
170
+ code = "\n".join(st.session_state["program_box"])
171
+ allowed_methods = {
172
+ name for name in dir(MXDBG) if not name.startswith("_")
173
+ }
174
+
175
+ # 用 AST 解析,确保所有 mxdbg 方法调用都在 allowlist 中
176
+ try:
177
+ tree = ast.parse(code)
178
+ except SyntaxError as e:
179
+ raise ValueError(f"语法错误: {e}")
180
+
181
+ for node in ast.walk(tree):
182
+ if isinstance(node, ast.Call):
183
+ func = node.func
184
+ if (
185
+ isinstance(func, ast.Attribute)
186
+ and isinstance(func.value, ast.Name)
187
+ and func.value.id == "mxdbg"
188
+ ):
189
+ if func.attr not in allowed_methods:
190
+ raise ValueError(f"不允许调用 mxdbg.{func.attr}")
191
+ elif isinstance(func, ast.Name) and func.id in (
192
+ "exec",
193
+ "eval",
194
+ "compile",
195
+ "__import__",
196
+ "open",
197
+ "breakpoint",
198
+ "help",
199
+ "input",
200
+ "getattr",
201
+ "setattr",
202
+ "delattr",
203
+ "type",
204
+ ):
205
+ raise ValueError(f"不允许调用 {func.id}()")
206
+ elif isinstance(func, ast.Attribute) and func.attr.startswith(
207
+ "__"
208
+ ):
209
+ raise ValueError(f"不允许访问 dunder 属性: {func.attr}")
210
+ elif isinstance(func, ast.Attribute):
211
+ # Check for chained dangerous attributes
212
+ parts = []
213
+ cur = func
214
+ while isinstance(cur, ast.Attribute):
215
+ parts.append(cur.attr)
216
+ cur = cur.value
217
+ if any(
218
+ p.startswith("__") or p in ("builtins",) for p in parts
219
+ ):
220
+ raise ValueError("不允许访问私有或 builtins 属性")
221
+ if (
222
+ isinstance(node, ast.Call)
223
+ and isinstance(node.func, ast.Attribute)
224
+ and (
225
+ node.func.attr.startswith("_")
226
+ or (
227
+ isinstance(node.func.value, ast.Attribute)
228
+ and node.func.value.attr.startswith("_")
229
+ )
230
+ )
231
+ ):
232
+ raise ValueError("不允许调用私有方法")
233
+ if isinstance(node, (ast.Import, ast.ImportFrom)):
234
+ raise ValueError("不允许导入模块") # noqa: TRY004 -- 安全检查拒绝,非类型错误
235
+
236
+ exec(code, {"mxdbg": mxdbg}) # noqa: S102 -- 受上方 AST 白名单约束(禁 import/私有/builtins)
237
+
238
+ # 获取执行结果并显示
239
+ sys.stdout = old_stdout
240
+ result_output.success("代码执行成功!")
241
+ st.text_area("执行结果", redirected_output.getvalue(), height=200)
242
+ except Exception as e:
243
+ sys.stdout = old_stdout
244
+ result_output.error(f"执行失败:{e}")
245
+ else:
246
+ st.warning("设备未连接,无法执行代码")
247
+
248
+ # 日志框
249
+ st.subheader("日志框")
250
+ log_output = st.empty()
251
+
252
+ # 自动刷新日志
253
+ if st.button("刷新日志"):
254
+ if mxdbg and getattr(mxdbg, "_client", None): # 确保 _client 存在且有效
255
+ log_data = (
256
+ mxdbg._client.read(mxdbg._client.in_waiting).decode("utf-8")
257
+ if mxdbg._client.in_waiting > 0
258
+ else "无日志"
259
+ )
260
+ log_output.text(log_data)
261
+ else:
262
+ st.warning("设备未连接或 _client 无效,无法获取日志")
paw3311dw.py ADDED
@@ -0,0 +1,110 @@
1
+ import sys
2
+
3
+ import numpy as np
4
+ from mxdbg import MXDBG
5
+ from PySide6.QtCore import QElapsedTimer, Qt, QTimer
6
+ from PySide6.QtGui import QImage, QPixmap
7
+ from PySide6.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget
8
+
9
+
10
+ class MainWindow(QMainWindow):
11
+ def __init__(self, dev=None):
12
+ super().__init__()
13
+
14
+ # Set up the main window layout and label to show the image
15
+ self.setWindowTitle("Live Image Display")
16
+
17
+ # Image display label
18
+ self.image_label = QLabel(self)
19
+ self.image_label.setAlignment(Qt.AlignCenter)
20
+
21
+ # Frame rate display label
22
+ self.fps_label = QLabel(self)
23
+ self.fps_label.setAlignment(Qt.AlignCenter)
24
+ self.fps_label.setStyleSheet("font-size: 20px; color: green;")
25
+
26
+ # Set up layout and central widget
27
+ layout = QVBoxLayout()
28
+ layout.addWidget(self.image_label)
29
+ layout.addWidget(self.fps_label) # Add FPS label to layout
30
+ central_widget = QWidget(self)
31
+ central_widget.setLayout(layout)
32
+ self.setCentralWidget(central_widget)
33
+
34
+ # Initialize MXDBG device
35
+ self.dev = dev if dev is not None else MXDBG()
36
+ ret, data = self.dev.usb_config(crc_enable=False)
37
+ print(ret, data)
38
+
39
+ ret, data = self.dev.spi_config(
40
+ freq=6000000,
41
+ miso_io_num=-1,
42
+ cs_ena_pretrans=1,
43
+ cs_ena_posttrans=1,
44
+ device_interface_flags=(
45
+ self.dev.spi_device["SPI_DEVICE_HALFDUPLEX"]
46
+ | self.dev.spi_device["SPI_DEVICE_3WIRE"]
47
+ ),
48
+ )
49
+ print(ret, data)
50
+
51
+ # Set up the timer for continuous updates
52
+ self.timer = QTimer(self)
53
+ self.timer.timeout.connect(self.update_image)
54
+ self.timer.start(100) # Update every 100ms
55
+
56
+ # Set up the elapsed timer to calculate FPS
57
+ self.elapsed_timer = QElapsedTimer()
58
+ self.elapsed_timer.start()
59
+ self.frames = 0
60
+
61
+ def update_image(self):
62
+ """Fetch new image data and update the display."""
63
+ _ret, data = self.dev.spi_read_image()
64
+
65
+ if len(data) == 900:
66
+ # Convert the data into a numpy array and reshape it to 30x30
67
+ data = np.array(data)
68
+ data = data.reshape(30, 30)
69
+
70
+ # Convert the numpy array to QImage
71
+ qimage = self.numpy_to_qimage(data)
72
+
73
+ # Resize image to 300x300 using SmoothTransformation (no loss)
74
+ scaled_qimage = qimage.scaled(
75
+ 300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation
76
+ )
77
+
78
+ # Update the QLabel with the new image
79
+ pixmap = QPixmap.fromImage(scaled_qimage)
80
+ self.image_label.setPixmap(pixmap)
81
+
82
+ # Increment frame count and update FPS every 1 second
83
+ self.frames += 1
84
+ if self.elapsed_timer.elapsed() >= 1000: # Every second
85
+ fps = self.frames
86
+ self.fps_label.setText(f"FPS: {fps}")
87
+ self.frames = 0 # Reset frame count
88
+ self.elapsed_timer.restart() # Restart the timer
89
+
90
+ def keyPressEvent(self, event):
91
+ if event.key() == Qt.Key_Q:
92
+ self.close()
93
+ super().keyPressEvent(event)
94
+
95
+ def numpy_to_qimage(self, np_array):
96
+ """Convert a numpy array to a QImage."""
97
+ height, width = np_array.shape
98
+ bytes_per_line = width
99
+ return QImage(
100
+ np_array.data, width, height, bytes_per_line, QImage.Format_Grayscale8
101
+ )
102
+
103
+
104
+ if __name__ == "__main__":
105
+ # Set up the application and the main window
106
+ app = QApplication(sys.argv)
107
+ window = MainWindow()
108
+ window.show()
109
+
110
+ sys.exit(app.exec())