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.
- firmware/bootloader.bin +0 -0
- firmware/mxdbg.bin +0 -0
- firmware/partition-table.bin +0 -0
- mxdbg-0.2.2.dist-info/METADATA +30 -0
- mxdbg-0.2.2.dist-info/RECORD +12 -0
- mxdbg-0.2.2.dist-info/WHEEL +5 -0
- mxdbg-0.2.2.dist-info/licenses/LICENSE +202 -0
- mxdbg-0.2.2.dist-info/top_level.txt +3 -0
- mxdbg.py +2050 -0
- mxdbg.toml +197 -0
- mxdbg_ui.py +262 -0
- paw3311dw.py +110 -0
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())
|