2 Commits
Author SHA1 Message Date
yrx 56125a8de4 wat 2026-09-04 11:13:45 +08:00
yrx 35fa4ad58c a23 change p19 2026-09-03 09:41:37 +08:00
18 changed files with 37 additions and 1380 deletions
-403
View File
@@ -1,403 +0,0 @@
import re
import hashlib
import binascii
from maix import time
from power import get_bus_voltage, voltage_to_percent
from urllib.parse import urlparse
from hardware import hardware_manager
class DownloadManager4G:
"""4g下载管理器(单例)"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(DownloadManager4G, cls).__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
# 私有状态
self.FRAG_SIZE = 1024
self.FRAG_DELAY = 10
self._initialized = True
def _log(self, *a):
if debug:
self.logger.debug(" ".join(str(x) for x in a))
def _pwr_log(self, prefix=""):
"""debug 用:输出电压/电量"""
if not debug:
return
try:
v = get_bus_voltage()
p = voltage_to_percent(v)
self.logger.debug(f"[PWR]{prefix} v={v:.3f}V p={p}%")
except Exception as e:
try:
self.logger.debug(f"[PWR]{prefix} read_failed: {e}")
except:
pass
def _clear_http_events(self):
if hardware_manager.at_client:
while hardware_manager.at_client.pop_http_event() is not None:
pass
def _parse_httpid(self, resp: str):
m = re.search(r"\+MHTTPCREATE:\s*(\d+)", resp)
return int(m.group(1)) if m else None
def _get_ip(self, ):
r = hardware_manager.at_client.send("AT+CGPADDR=1", "OK", 3000)
m = re.search(r'\+CGPADDR:\s*1,"([^"]+)"', r)
return m.group(1) if m else ""
def _ensure_pdp(self, ):
ip = self._get_ip()
if ip and ip != "0.0.0.0":
return True, ip
hardware_manager.at_client.send("AT+MIPCALL=1,1", "OK", 15000)
for _ in range(10):
ip = self._get_ip()
if ip and ip != "0.0.0.0":
return True, ip
time.sleep(1)
return False, ip
def _extract_hdr_fields(self, hdr_text: str):
mlen = re.search(r"Content-Length:\s*(\d+)", hdr_text, re.IGNORECASE)
clen = int(mlen.group(1)) if mlen else None
mmd5 = re.search(r"Content-Md5:\s*([A-Za-z0-9+/=]+)", hdr_text, re.IGNORECASE)
md5_b64 = mmd5.group(1).strip() if mmd5 else None
return clen, md5_b64
def _extract_content_range(self, hdr_text: str):
m = re.search(r"Content-Range:\s*bytes\s*(\d+)\s*-\s*(\d+)\s*/\s*(\d+)", hdr_text, re.IGNORECASE)
if not m:
return None, None, None
try:
return int(m.group(1)), int(m.group(2)), int(m.group(3))
except:
return None, None, None
def _hard_reset_http(self, ):
"""模块进入"坏状态"时的保守清场"""
self._clear_http_events()
for i in range(0, 6):
try:
hardware_manager.at_client.send(f"AT+MHTTPDEL={i}", "OK", 1200)
except:
pass
self._clear_http_events()
def _create_httpid(self, full_reset=False):
self._clear_http_events()
if hardware_manager.at_client:
hardware_manager.at_client.flush()
if full_reset:
self._hard_reset_http()
resp = hardware_manager.at_client.send(f'AT+MHTTPCREATE="{base_url}"', "OK", 8000)
hid = self._parse_httpid(resp)
if self._is_https:
resp = hardware_manager.at_client.send(f'AT+MHTTPCFG="ssl",{hid},1,1', "OK", 2000)
if "ERROR" in resp or "CME ERROR" in resp:
self.logger.error(f"MHTTPCFG SSL failed: {resp}")
# 尝试https 降级到http
downgraded_base_url = base_url.replace("https://", "http://")
resp = hardware_manager.at_client.send(f'AT+MHTTPCREATE="{downgraded_base_url}"', "OK", 8000)
hid = self._parse_httpid(resp)
return hid, resp
def _fetch_range_into_buf(self, start, want_len, out_buf, path, full_reset=False):
"""
请求 Range [start, start+want_len),写入 out_bufbytearray,长度=want_len
返回 (ok, msg, total_len, md5_b64, got_len)
"""
end_incl = start + want_len - 1
hid, cresp = self._create_httpid(full_reset=full_reset)
if hid is None:
return False, f"MHTTPCREATE failed: {cresp}", None, None, 0
# 降低 URC 压力(分片/延迟)
hardware_manager.at_client.send(f'AT+MHTTPCFG="fragment",{hid},{self.FRAG_SIZE},{self.FRAG_DELAY}', "OK", 1500)
# 设置 Range headerinclusive
hardware_manager.at_client.send(f'AT+MHTTPCFG="header",{hid},"Range: bytes={start}-{end_incl}"', "OK", 3000)
req = hardware_manager.at_client.send(f'AT+MHTTPREQUEST={hid},1,0,"{path}"', "OK", 15000)
if "ERROR" in req or "CME ERROR" in req:
hardware_manager.at_client.send(f"AT+MHTTPDEL={hid}", "OK", 2000)
return False, f"MHTTPREQUEST failed: {req}", None, None, 0
# 等 header + content
hdr_text = None
hdr_accum = ""
code = None
resp_total = None
total_len = None
md5_b64 = None
got_ranges = set()
last_sum = 0
t0 = time.ticks_ms()
timeout_ms = 9000
logged_hdr = False
while time.ticks_ms() - t0 < timeout_ms:
ev = hardware_manager.at_client.pop_http_event() if hardware_manager.at_client else None
if not ev:
time.sleep_ms(5)
continue
if ev[0] == "header":
_, ehid, ecode, ehdr = ev
if ehid != hid:
continue
code = ecode
hdr_text = ehdr
if ehdr:
hdr_accum = (hdr_accum + "\n" + ehdr) if hdr_accum else ehdr
resp_total_tmp, md5_tmp = self._extract_hdr_fields(hdr_accum)
if md5_tmp:
md5_b64 = md5_tmp
cr_s, cr_e, cr_total = self._extract_content_range(hdr_accum)
if cr_total is not None:
total_len = cr_total
if resp_total_tmp is not None:
resp_total = resp_total_tmp
elif resp_total is None and (cr_s is not None) and (cr_e is not None) and (cr_e >= cr_s):
resp_total = (cr_e - cr_s + 1)
if (not logged_hdr) and (resp_total is not None or total_len is not None):
self._log(f"[HDR] id={hid} code={code} clen={resp_total} cr={cr_s}-{cr_e}/{cr_total}")
logged_hdr = True
continue
if ev[0] == "content":
_, ehid, _total, _sum, _cur, payload = ev
if ehid != hid:
continue
if resp_total is None:
resp_total = _total
if resp_total is None or resp_total <= 0:
continue
start_rel = _sum - _cur
end_rel = _sum
if start_rel < 0 or start_rel >= resp_total:
continue
if end_rel > resp_total:
end_rel = resp_total
actual_len = min(len(payload), end_rel - start_rel)
if actual_len <= 0:
continue
out_buf[start_rel:start_rel + actual_len] = payload[:actual_len]
got_ranges.add((start_rel, start_rel + actual_len))
if _sum > last_sum:
last_sum = _sum
if debug and (last_sum >= resp_total or (last_sum % 512 == 0)):
self._log(f"[CHUNK] {start}+{last_sum}/{resp_total}")
if last_sum >= resp_total:
break
# 清理实例(快路径:只删当前 hid)
try:
hardware_manager.at_client.send(f"AT+MHTTPDEL={hid}", "OK", 2000)
except:
pass
if resp_total is None:
return False, "no_header_or_total", total_len, md5_b64, 0
# 计算实际填充长度
merged = sorted(got_ranges)
merged2 = []
for s, e in merged:
if not merged2 or s > merged2[-1][1]:
merged2.append((s, e))
else:
merged2[-1] = (merged2[-1][0], max(merged2[-1][1], e))
filled = sum(e - s for s, e in merged2)
if filled < resp_total:
return False, f"incomplete_chunk got={filled} expected={resp_total} code={code}", total_len, md5_b64, filled
got_len = resp_total
return True, "OK", total_len, md5_b64, got_len
def download_file_via_4g(self, url, filename,
total_timeout_ms=600000,
retries=3,
debug=False):
"""
ML307R HTTP 下载(更稳的"固定小块 Range 顺序下载",基于main109.py):
- 只依赖 +MHTTPURC:"header"/"content"(不依赖 MHTTPREAD/cached
- 每次只请求一个小块 Range(默认 10240B),失败就重试同一块,必要时缩小块大小
- 每个 chunk 都重新 MHTTPCREATE/MHTTPREQUEST,避免卡在"206 header 但不吐 content"的坏状态
- 使用二进制模式下载,确保文件完整性
"""
# 小块策略(与main109.py保持一致)
CHUNK_MAX = 10240
CHUNK_MIN = 128
CHUNK_RETRIES = 12
t_func0 = time.ticks_ms()
parsed = urlparse(url)
host = parsed.hostname
path = parsed.path or "/"
if parsed.query:
path = f"{path}?{parsed.query}"
if parsed.fragment:
path = f"{path}#{parsed.fragment}"
if not host:
return False, "bad_url (no host)"
if isinstance(url, str) and url.startswith("https://static.shelingxingqiu.com/"):
base_url = "https://static.shelingxingqiu.com"
# TODO:使用https,看看是否能成功
self._is_https = True
else:
base_url = f"http://{host}"
self._is_https = False
try:
self._begin_ota()
except:
pass
from network import network_manager
with network_manager.get_uart_lock():
try:
ok_pdp, ip = self._ensure_pdp()
if not ok_pdp:
return False, f"PDP not ready (ip={ip})"
# 先清空旧事件,避免串台
self._clear_http_events()
# 为了支持随机写入,先创建空文件
try:
with open(filename, "wb") as f:
f.write(b"")
except Exception as e:
return False, f"open_file_failed: {e}"
total_len = None
expect_md5_b64 = None
offset = 0
chunk = CHUNK_MAX
t_start = time.ticks_ms()
last_progress_ms = t_start
STALL_TIMEOUT_MS = 60000
last_pwr_ms = t_start
self._pwr_log(prefix=" ota_start")
bad_http_state = 0
while True:
now = time.ticks_ms()
if debug and time.ticks_diff(now, last_pwr_ms) >= 5000:
last_pwr_ms = now
self._pwr_log(prefix=f" off={offset}/{total_len or '?'}")
if time.ticks_diff(now, t_start) > total_timeout_ms:
return False, f"timeout overall after {total_timeout_ms}ms offset={offset} total={total_len}"
if time.ticks_diff(now, last_progress_ms) > STALL_TIMEOUT_MS:
return False, f"timeout stalled {STALL_TIMEOUT_MS}ms offset={offset} total={total_len}"
if total_len is not None and offset >= total_len:
break
want = chunk
if total_len is not None:
remain = total_len - offset
if remain <= 0:
break
if want > remain:
want = remain
# 本 chunk 的 buffer(长度=want
buf = bytearray(want)
success = False
last_err = "unknown"
md5_seen = None
got_len = 0
for k in range(1, CHUNK_RETRIES + 1):
do_full_reset = (bad_http_state >= 2)
ok, msg, tlen, md5_b64, got = self._fetch_range_into_buf(offset, want, buf, base_url, path, full_reset=do_full_reset)
last_err = msg
if tlen is not None and total_len is None:
total_len = tlen
if md5_b64 and not expect_md5_b64:
expect_md5_b64 = md5_b64
if ok:
success = True
got_len = got
bad_http_state = 0
break
try:
if ("no_header_or_total" in msg) or ("MHTTPREQUEST failed" in msg) or (
"MHTTPCREATE failed" in msg):
bad_http_state += 1
else:
bad_http_state = max(0, bad_http_state - 1)
except:
pass
if chunk > CHUNK_MIN:
chunk = max(CHUNK_MIN, chunk // 2)
want = min(chunk, want)
buf = bytearray(want)
self._log(f"[RETRY] off={offset} want={want} try={k} err={msg}")
self._pwr_log(prefix=f" retry{k} off={offset}")
time.sleep_ms(120)
if not success:
return False, f"chunk_failed off={offset} want={want} err={last_err} total={total_len}"
# 写入文件(二进制模式)
try:
with open(filename, "r+b") as f:
f.seek(offset)
f.write(bytes(buf))
except Exception as e:
return False, f"write_failed off={offset}: {e}"
offset += len(buf)
last_progress_ms = time.ticks_ms()
chunk = CHUNK_MAX
if debug:
self._log(f"[OK] offset={offset}/{total_len or '?'}")
# MD5 校验
if expect_md5_b64 and hashlib is not None:
try:
with open(filename, "rb") as f:
data = f.read()
digest = hashlib.md5(data).digest()
got_b64 = binascii.b2a_base64(digest).decode().strip()
if got_b64 != expect_md5_b64:
return False, f"md5_mismatch got={got_b64} expected={expect_md5_b64}"
self.logger.debug(f"[4G-DL] MD5 verified: {got_b64}")
except Exception as e:
return False, f"md5_check_failed: {e}"
t_cost = time.ticks_diff(time.ticks_ms(), t_func0)
self.logger.info(f"[4G-DL] download complete: size={offset} ip={ip} cost_ms={t_cost}")
return True, f"OK size={offset} ip={ip} cost_ms={t_cost}"
finally:
self._end_ota()
-450
View File
@@ -1,450 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
4G Image Upload Manager
Uploads images to Qiniu cloud via ML307R 4G module TCP socket (MIPOPEN + MIPSEND).
AT Command Sequence (ML307R TCP socket POST):
AT+MIPCALL=1,1 // Ensure PDP context active
AT+MIPCLOSE=<id> // Close old socket (ignore error)
AT+MIPOPEN=<id>,"TCP","<host>",80 // Open TCP socket
// Wait for +MIPOPEN: <id>,0 (success)
AT+MIPSEND=<id>,<len> // Send data
// Wait for ">" prompt, then write raw bytes
// Repeat MIPSEND for all chunks
// Wait for +MIPURC: "rtcp" response
AT+MIPCLOSE=<id> // Close socket
"""
import re
import os
import json
from maix import time
from urllib.parse import urlparse
from logger_manager import logger_manager
from hardware import hardware_manager
# Multipart form boundary (simple alphanumeric to avoid AT command parser issues)
BOUNDARY = "QiniuFormBoundary" + hex(int(time.time()))[2:]
# Chunk size for MIPSEND (max 1024 to avoid AT line buffer limits)
SEND_CHUNK = 1024
# Socket ID for upload (dedicated to avoid conflict with main app TCP)
UPLOAD_SOCK_ID = 3
class FourGUploadManager:
"""4G image upload manager using ML307R TCP socket (MIPOPEN + MIPSEND)"""
def __init__(self, at_client):
"""Initialize with AT client instance"""
self.at = at_client
self.logger = logger_manager.logger
# ------------------------------------------------------------------ logging
def _log(self, msg):
try:
self.logger.debug("[4G-UL] " + msg)
except Exception:
print("[4G-UL] " + msg)
def _log_info(self, msg):
try:
self.logger.info("[4G-UL] " + msg)
except Exception:
print("[4G-UL] " + msg)
def _log_error(self, msg):
try:
self.logger.error("[4G-UL] " + msg)
except Exception:
print("[4G-UL] " + msg)
# --------------------------------------------------------------- helpers
def _ensure_pdp(self):
"""Ensure PDP context is active; returns (ok, ip)"""
r = self.at.send("AT+CGPADDR=1", "OK", 3000)
m = re.search(r'\+CGPADDR:\s*1,"([^"]+)"', r)
ip = m.group(1) if m else ""
if ip and ip != "0.0.0.0":
return True, ip
self.at.send("AT+MIPCALL=1,1", "OK", 15000)
for _ in range(10):
r = self.at.send("AT+CGPADDR=1", "OK", 3000)
m = re.search(r'\+CGPADDR:\s*1,"([^"]+)"', r)
ip = m.group(1) if m else ""
if ip and ip != "0.0.0.0":
return True, ip
time.sleep(1)
return False, ip
def _is_error(self, resp):
"""Check AT response for any error indicators"""
return "ERROR" in resp or "CME ERROR" in resp
# --------------------------------------------------------- multipart body
def _build_multipart_body(self, image_path, upload_token, key):
"""
Build multipart/form-data body as bytes for Qiniu upload.
Fields:
- token : Qiniu upload token
- key : object key in bucket
- file : binary image data
"""
boundary = BOUNDARY.encode()
with open(image_path, "rb") as f:
file_data = f.read()
filename = os.path.basename(image_path)
ext = os.path.splitext(image_path)[1].lower()
ct_map = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".bmp": "image/bmp",
".webp": "image/webp",
}
content_type = ct_map.get(ext, "application/octet-stream")
body = bytearray()
# -- token field --
body += b"--" + boundary + b"\r\n"
body += b'Content-Disposition: form-data; name="token"\r\n'
body += b"\r\n"
body += upload_token.encode("utf-8") + b"\r\n"
# -- key field --
body += b"--" + boundary + b"\r\n"
body += b'Content-Disposition: form-data; name="key"\r\n'
body += b"\r\n"
body += key.encode("utf-8") + b"\r\n"
# -- file field --
body += b"--" + boundary + b"\r\n"
body += (
b'Content-Disposition: form-data; name="file"; filename="'
+ filename.encode("utf-8")
+ b'"\r\n'
)
body += b"Content-Type: " + content_type.encode("utf-8") + b"\r\n"
body += b"\r\n"
body += file_data + b"\r\n"
# -- closing boundary --
body += b"--" + boundary + b"--\r\n"
return bytes(body)
# --------------------------------------------------- TCP socket helpers
def _close_socket(self, sock_id):
"""Close socket, ignore CME ERROR 55 (already closed)"""
try:
resp = self.at.send("AT+MIPCLOSE=" + str(sock_id), "OK", 5000)
self._log("socket " + str(sock_id) + " closed: " + resp)
except Exception as e:
# Ignore CME ERROR 55 (socket not open)
self._log("socket close (may already be closed): " + str(e))
def _open_socket(self, sock_id, host, port):
"""
Open TCP socket to host:port.
Returns (success, error_msg)
"""
cmd = 'AT+MIPOPEN=' + str(sock_id) + ',"TCP","' + host + '",' + str(port)
resp = self.at.send(cmd, "OK", 15000)
if self._is_error(resp):
return False, "MIPOPEN failed: " + resp
# Wait for +MIPOPEN: <id>,0 (success) or +MIPOPEN: <id>,<error_code>
# The URC may come in the same response or separately
mipopen_pattern = r"\+MIPOPEN:\s*" + str(sock_id) + r",(\d+)"
m = re.search(mipopen_pattern, resp)
if m:
result_code = int(m.group(1))
if result_code == 0:
return True, ""
else:
return False, "MIPOPEN error code: " + str(result_code)
# If not in initial response, wait for URC
try:
urc_resp = self.at.send("", "+MIPOPEN:", 15000)
m = re.search(mipopen_pattern, urc_resp)
if m:
result_code = int(m.group(1))
if result_code == 0:
return True, ""
else:
return False, "MIPOPEN error code: " + str(result_code)
except Exception as e:
return False, "MIPOPEN URC timeout: " + str(e)
return False, "MIPOPEN no response"
def _send_chunk(self, sock_id, chunk):
"""
Send a single chunk via MIPSEND.
Thread safety is provided by the outer network_manager.get_uart_lock().
NOTE: Do NOT add self.at._cmd_lock here — self.at.send() already
acquires it internally and threading.Lock is not reentrant.
Returns (success, error_msg)
"""
chunk_len = len(chunk)
# Step 1: Send AT+MIPSEND command and wait for ">" prompt
cmd = "AT+MIPSEND=" + str(sock_id) + "," + str(chunk_len)
try:
resp = self.at.send(cmd, ">", 3000)
if ">" not in resp:
return False, "MIPSEND no > prompt: " + resp
except Exception as e:
return False, "MIPSEND > prompt error: " + str(e)
# Step 2: Write raw binary bytes directly to UART
# Must be done immediately after ">" prompt, no lock re-acquisition
try:
self.at.uart.write(chunk)
except Exception as e:
return False, "MIPSEND write error: " + str(e)
# Step 3: Wait for OK or SEND OK confirmation
try:
confirm_resp = self.at.send("", "OK", 8000)
if self._is_error(confirm_resp):
return False, "MIPSEND confirmation error: " + confirm_resp
except Exception as e:
return False, "MIPSEND confirmation timeout: " + str(e)
return True, ""
def _send_data(self, sock_id, data):
"""
Send data in chunks via MIPSEND.
Returns (success, error_msg)
"""
total_len = len(data)
offset = 0
chunk_num = 0
while offset < total_len:
end = min(offset + SEND_CHUNK, total_len)
chunk = data[offset:end]
ok, err = self._send_chunk(sock_id, chunk)
if not ok:
return False, "Chunk " + str(chunk_num) + " failed: " + err
chunk_num += 1
offset = end
if chunk_num % 10 == 0 or offset >= total_len:
self._log(
"send progress: "
+ str(offset) + "/" + str(total_len)
+ " bytes (" + str(chunk_num) + " chunks)"
)
self._log("all data sent: " + str(chunk_num) + " chunks, " + str(total_len) + " bytes")
return True, ""
def _wait_for_response(self, sock_id, timeout_ms=30000):
"""
Wait for +MIPURC: "rtcp" response.
Returns (success, status_code, body, error_msg)
"""
pattern = r'\+MIPURC:\s*"rtcp",\s*' + str(sock_id) + r',\s*(\d+),'
t0 = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms:
try:
# Try to get response with short timeout
resp = self.at.send("", "+MIPURC:", 1000)
m = re.search(pattern, resp)
if m:
data_len = int(m.group(1))
# Extract HTTP response data after the URC header
# Format: +MIPURC: "rtcp",<sock_id>,<len>,<data>
urc_end = resp.find("+MIPURC:")
if urc_end >= 0:
# Find the data after the length field
match_end = m.end()
http_data = resp[match_end:match_end + data_len]
# Parse HTTP status line
status_match = re.search(r"HTTP/\d\.\d\s+(\d+)", http_data)
status_code = int(status_match.group(1)) if status_match else None
# Extract body (after headers)
header_end = http_data.find("\r\n\r\n")
if header_end >= 0:
body = http_data[header_end + 4:]
else:
body = http_data
return True, status_code, body, ""
except Exception:
pass
time.sleep_ms(100)
return False, None, "", "Response timeout"
def _build_http_request(self, host, body_bytes):
"""
Build full HTTP POST request as bytes.
"""
headers = (
"POST / HTTP/1.1\r\n"
"Host: " + host + "\r\n"
"Content-Type: multipart/form-data; boundary=" + BOUNDARY + "\r\n"
"Content-Length: " + str(len(body_bytes)) + "\r\n"
"Connection: close\r\n"
"\r\n"
)
return headers.encode("utf-8") + body_bytes
# ============================================================ public API
def upload_file(self, file_path, upload_url, upload_token, key):
"""Generic file upload to Qiniu cloud via 4G TCP socket POST.
Args:
file_path: Local path to any file
upload_url: Qiniu upload URL
upload_token: Qiniu upload token
key: File key in Qiniu bucket
Returns:
dict with 'success' bool and 'key'/'error' fields
"""
return self.upload_image(file_path, upload_url, upload_token, key)
def upload_image(self, image_path, upload_url, upload_token, key):
"""
Upload image to Qiniu cloud via 4G TCP socket POST.
Args:
image_path: Local path to image file
upload_url: Qiniu upload URL (e.g., "https://upload.qiniup.com")
upload_token: Qiniu upload token
key: File key in Qiniu (e.g., "shootPic/device01/shoot01.png")
Returns:
dict with 'success' bool and 'key'/'error' fields
"""
if not self.at:
return {"success": False, "error": "AT client not available"}
if not os.path.exists(image_path):
return {"success": False, "error": "Image file not found: " + image_path}
# Force HTTP for 4G module (extract hostname, use port 80)
parsed = urlparse(upload_url)
host = parsed.hostname
if not host:
return {"success": False, "error": "Invalid upload URL: " + upload_url}
if upload_url.lower().startswith("https://"):
self._log_info("Converted HTTPS->HTTP for 4G module")
file_size = os.path.getsize(image_path)
self._log_info(
"upload: " + image_path + " (" + str(file_size) + "B) -> "
+ host + " key=" + key
)
from network import network_manager
with network_manager.get_uart_lock():
try:
# ---- Step 1: Ensure PDP context ----
ok_pdp, ip = self._ensure_pdp()
if not ok_pdp:
return {"success": False, "error": "PDP not ready (ip=" + str(ip) + ")"}
# ---- Step 2: Close old socket ----
self._close_socket(UPLOAD_SOCK_ID)
# ---- Step 3: Open TCP socket ----
ok, err = self._open_socket(UPLOAD_SOCK_ID, host, 80)
if not ok:
return {"success": False, "error": "Socket open failed: " + err}
try:
# ---- Step 4: Build multipart body and HTTP request ----
body = self._build_multipart_body(image_path, upload_token, key)
http_request = self._build_http_request(host, body)
self._log("HTTP request size: " + str(len(http_request)) + " bytes")
# ---- Step 5: Send data via MIPSEND ----
ok, err = self._send_data(UPLOAD_SOCK_ID, http_request)
if not ok:
return {"success": False, "error": "Send failed: " + err}
# ---- Step 6: Wait for response ----
ok, status_code, resp_body, err = self._wait_for_response(UPLOAD_SOCK_ID)
if not ok:
return {"success": False, "error": "Response error: " + err}
# ---- Step 7: Parse response ----
if status_code is None:
return {"success": False, "error": "No HTTP status in response"}
if 200 <= status_code < 300:
try:
resp_json = json.loads(resp_body)
resp_key = resp_json.get("key", key)
self._log_info("upload success: key=" + resp_key + " code=" + str(status_code))
return {"success": True, "key": resp_key}
except Exception as e:
self._log_error("response parse error: " + str(e))
return {
"success": True,
"key": key,
"raw": resp_body,
}
else:
self._log_error(
"HTTP error: code=" + str(status_code) + " body=" + resp_body[:200]
)
return {
"success": False,
"error": "HTTP " + str(status_code),
"response": resp_body,
}
finally:
# ---- Step 8: Always close socket ----
self._close_socket(UPLOAD_SOCK_ID)
except Exception as e:
self._log_error("upload exception: " + str(e))
return {"success": False, "error": str(e)}
# ====================================================================== demo
if __name__ == "__main__":
# Demo usage — requires actual ML307R 4G module hardware to run.
print("FourGUploadManager - requires ML307R 4G module hardware")
print()
print("Usage:")
print(" from hardware import hardware_manager")
print(" from at_client import ATClient")
print(" from maix import uart")
print()
print(" # Initialize UART and AT client (normally done in hardware init)")
print(" uart4g = uart.UART('/dev/ttyS1', 115200, ...)")
print(" at_client = ATClient(uart4g)")
print(" at_client.start()")
print()
print(" # Upload image to Qiniu")
print(" uploader = FourGUploadManager(at_client)")
print(" result = uploader.upload_image(")
print(" image_path='/maixapp/apps/t11/shoot.png',")
print(" upload_url='https://upload.qiniup.com',")
print(" upload_token='<qiniu_upload_token>',")
print(" key='shootPic/device01/shoot01.png'")
print(" )")
print(" print('Upload result:', result)")
Binary file not shown.
Binary file not shown.
+3 -5
View File
@@ -1,12 +1,10 @@
id: t11 id: t11
name: t11 name: t11
version: 3.0.5 version: 3.0.3
author: t11 author: t11
icon: '' icon: ''
desc: t11 desc: t11
files: files:
- 4g_download_manager.py
- 4g_upload_manager.py
- app.yaml - app.yaml
- archery_netcore.cpython-311-riscv64-linux-gnu.so - archery_netcore.cpython-311-riscv64-linux-gnu.so
- at_client.py - at_client.py
@@ -18,8 +16,8 @@ files:
- laser_manager.py - laser_manager.py
- logger_manager.py - logger_manager.py
- main.py - main.py
- model_285484.cvimodel - model_317828.cvimodel
- model_285484.mud - model_317828.mud
- network.py - network.py
- ota_curl.sh - ota_curl.sh
- ota_manager.py - ota_manager.py
+1 -17
View File
@@ -8,15 +8,6 @@ import threading
import config import config
from logger_manager import logger_manager from logger_manager import logger_manager
_USE_CV = False
try:
import cv2
import numpy as np
from maix import image as _maix_image
_USE_CV = True
except ImportError:
pass
class CameraManager: class CameraManager:
"""相机管理器(单例)""" """相机管理器(单例)"""
@@ -66,12 +57,6 @@ class CameraManager:
with self._camera_lock: with self._camera_lock:
if self._camera is None: if self._camera is None:
self._camera = camera.Camera(width, height) self._camera = camera.Camera(width, height)
v_flip = getattr(config, 'CAMERA_V_FLIP', False)
h_mirror = getattr(config, 'CAMERA_H_MIRROR', False)
if v_flip:
self._camera.vflip(1)
if h_mirror:
self._camera.hmirror(1)
return self._camera return self._camera
@@ -116,8 +101,7 @@ class CameraManager:
with self._camera_lock: with self._camera_lock:
if self._camera is None: if self._camera is None:
self.init_camera() self.init_camera()
frame = self._camera.read() return self._camera.read()
return frame
def show(self, image): def show(self, image):
""" """
+1 -3
View File
@@ -15,8 +15,6 @@ LOCAL_FILENAME = APP_DIR + "/main_tmp.py"
# 相机初始化分辨率(CameraManager / main.py 使用) # 相机初始化分辨率(CameraManager / main.py 使用)
CAMERA_WIDTH = 640 CAMERA_WIDTH = 640
CAMERA_HEIGHT = 480 CAMERA_HEIGHT = 480
CAMERA_V_FLIP = True # 摄像头垂直翻转(上下颠倒时设为 True)
CAMERA_H_MIRROR = True # 摄像头水平镜像(左右反了时设为 True)
# 三角形检测缩图比例:默认按相机最长边缩到 1/2(性能更稳;可按需调整) # 三角形检测缩图比例:默认按相机最长边缩到 1/2(性能更稳;可按需调整)
# 取值范围建议 (0.25 ~ 1.0]1.0 表示不缩图 # 取值范围建议 (0.25 ~ 1.0]1.0 表示不缩图
@@ -271,7 +269,7 @@ TRIANGLE_YOLO_PRELOAD_ON_BOOT = False
# YOLO target size classification: class 0=20cm, class 1=40cm. # YOLO target size classification: class 0=20cm, class 1=40cm.
TARGET_CLASS_YOLO_ENABLE = True TARGET_CLASS_YOLO_ENABLE = True
TARGET_CLASS_YOLO_MODEL_PATH = APP_DIR + "/model_285484.mud" TARGET_CLASS_YOLO_MODEL_PATH = APP_DIR + "/model_317828.mud"
TARGET_CLASS_YOLO_LABELS = (20, 40) TARGET_CLASS_YOLO_LABELS = (20, 40)
TARGET_CLASS_YOLO_CONF_TH = 0.50 TARGET_CLASS_YOLO_CONF_TH = 0.50
TARGET_CLASS_YOLO_IOU_TH = 0.45 TARGET_CLASS_YOLO_IOU_TH = 0.45
-7
View File
@@ -57,16 +57,9 @@ PYBIND11_MODULE(archery_netcore, m) {
"Pack TCP packet: header (len+type+checksum) + JSON body", "Pack TCP packet: header (len+type+checksum) + JSON body",
py::arg("msg_type"), py::arg("body_dict")); py::arg("msg_type"), py::arg("body_dict"));
m.def("make_packet_pb", &netcore::make_packet_pb,
"Pack TCP packet: header (len+type+checksum) + raw bytes body (for protobuf)",
py::arg("msg_type"), py::arg("body_bytes"));
m.def("parse_packet", &netcore::parse_packet, m.def("parse_packet", &netcore::parse_packet,
"Parse TCP packet, return (msg_type, body_dict)"); "Parse TCP packet, return (msg_type, body_dict)");
m.def("parse_packet_raw", &netcore::parse_packet_raw,
"Parse TCP packet, return (msg_type, body_bytes) without JSON parsing");
m.def("get_config", &get_config, "Get system configuration"); m.def("get_config", &get_config, "Get system configuration");
m.def( m.def(
-59
View File
@@ -51,43 +51,6 @@ namespace netcore {
return py::bytes(reinterpret_cast<const char*>(packet.data()), packet.size()); return py::bytes(reinterpret_cast<const char*>(packet.data()), packet.size());
} }
// 打包 TCP 数据包 (raw bytes body, 用于 protobuf)
py::bytes make_packet_pb(int msg_type, py::bytes body_bytes) {
netcore::log_debug(std::string("make_packet_pb msg_type=") + std::to_string(msg_type));
py::buffer_info buf = py::buffer(body_bytes).request();
uint32_t body_len = buf.size;
uint32_t checksum = body_len + msg_type;
std::vector<uint8_t> packet;
packet.reserve(12 + body_len);
// body_len (big-endian, 4 bytes)
packet.push_back((body_len >> 24) & 0xFF);
packet.push_back((body_len >> 16) & 0xFF);
packet.push_back((body_len >> 8) & 0xFF);
packet.push_back(body_len & 0xFF);
// msg_type (big-endian, 4 bytes)
packet.push_back((msg_type >> 24) & 0xFF);
packet.push_back((msg_type >> 16) & 0xFF);
packet.push_back((msg_type >> 8) & 0xFF);
packet.push_back(msg_type & 0xFF);
// checksum (big-endian, 4 bytes)
packet.push_back((checksum >> 24) & 0xFF);
packet.push_back((checksum >> 16) & 0xFF);
packet.push_back((checksum >> 8) & 0xFF);
packet.push_back(checksum & 0xFF);
// 追加 body bytes
const uint8_t* body_ptr = static_cast<const uint8_t*>(buf.ptr);
packet.insert(packet.end(), body_ptr, body_ptr + body_len);
netcore::log_debug(std::string("make_packet_pb done bytes=") + std::to_string(packet.size()));
return py::bytes(reinterpret_cast<const char*>(packet.data()), packet.size());
}
// 解析 TCP 数据包 // 解析 TCP 数据包
py::tuple parse_packet(py::bytes data) { py::tuple parse_packet(py::bytes data) {
// 1) 转换为 bytes view // 1) 转换为 bytes view
@@ -147,26 +110,4 @@ namespace netcore {
return py::make_tuple(py::int_(msg_type), raw_dict); return py::make_tuple(py::int_(msg_type), raw_dict);
} }
} }
// 解析 TCP 数据包 -> (msg_type, body_bytes) 不做 JSON 解析
py::tuple parse_packet_raw(py::bytes data) {
py::buffer_info buf = py::buffer(data).request();
if (buf.size < 12) {
return py::make_tuple(py::none(), py::none());
}
const uint8_t* ptr = static_cast<const uint8_t*>(buf.ptr);
uint32_t body_len = (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
uint32_t msg_type = (ptr[4] << 24) | (ptr[5] << 16) | (ptr[6] << 8) | ptr[7];
uint32_t expected_len = 12 + body_len;
if (buf.size < expected_len) {
return py::make_tuple(py::none(), py::none());
}
// 返回原始 body bytes(不做 JSON 解析)
py::bytes body_bytes(reinterpret_cast<const char*>(ptr + 12), body_len);
return py::make_tuple(py::int_(msg_type), body_bytes);
}
} }
+2 -9
View File
@@ -7,15 +7,8 @@ namespace py = pybind11;
namespace netcore { namespace netcore {
// 打包 TCP 数据包 (JSON body) // 打包 TCP 数据包
py::bytes make_packet(int msg_type, py::dict body_dict); py::bytes make_packet(int msg_type, py::dict body_dict);
// 解包 TCP 数据包
// 打包 TCP 数据包 (raw bytes body, 用于 protobuf)
py::bytes make_packet_pb(int msg_type, py::bytes body_bytes);
// 解包 TCP 数据包 -> (msg_type, body_dict)
py::tuple parse_packet(py::bytes data); py::tuple parse_packet(py::bytes data);
// 解包 TCP 数据包 -> (msg_type, body_bytes) 不做 JSON 解析
py::tuple parse_packet_raw(py::bytes data);
} }
+16 -12
View File
@@ -136,7 +136,6 @@ def cmd_str():
sync_system_time_from_4g() sync_system_time_from_4g()
# 2.1 WiFi 热点配网兜底:仅当 STA 与 4G 均不可用时起 AP + HTTP;提交后删 /boot/wifi.ap、建 wifi.sta 并 reboot # 2.1 WiFi 热点配网兜底:仅当 STA 与 4G 均不可用时起 AP + HTTP;提交后删 /boot/wifi.ap、建 wifi.sta 并 reboot
_ota_pending_path = f"{config.APP_DIR}/ota_pending.json"
try: try:
from wifi_config_httpd import maybe_start_wifi_ap_fallback from wifi_config_httpd import maybe_start_wifi_ap_fallback
@@ -287,13 +286,12 @@ def cmd_str():
logger.info("系统准备完成...") logger.info("系统准备完成...")
last_adc_trigger = 0 last_adc_trigger = 0
trigger_adc_val = 0 # 触发时的气压值,气压需降回此值以下才能再次触发
# 读取一次ADC初始值,防止开机时传感器已有压力导致误触发 # 读取一次ADC初始值,防止开机时传感器已有压力导致误触发
enable_check = True
try: try:
last_adc_val = hardware_manager.adc_obj.read() last_adc_val = hardware_manager.adc_obj.read()
except Exception: except Exception:
last_adc_val = 0 last_adc_val = 0
peak_adc_val = 0 # 当前周期内的压力峰值
# 气压采样:减少日志频率(每 N 个点输出一条),避免 logger.debug 拖慢采样 # 气压采样:减少日志频率(每 N 个点输出一条),避免 logger.debug 拖慢采样
PRESSURE_BATCH_SIZE = 100 PRESSURE_BATCH_SIZE = 100
@@ -383,16 +381,22 @@ def cmd_str():
pressure_max = adc_val pressure_max = adc_val
if len(pressure_buf) >= PRESSURE_BATCH_SIZE: if len(pressure_buf) >= PRESSURE_BATCH_SIZE:
_flush_pressure_buf("batch") _flush_pressure_buf("batch")
# 突变增量检测:压力增量大于300时触发 # 峰值检测:压力从峰值下降时触发,确保捕获到最大冲击时刻
# 触发后需等气压降到触发值以下才重新检测增量 if adc_val > peak_adc_val:
if adc_val < trigger_adc_val : peak_adc_val = adc_val # 更新峰值
enable_check = True if (peak_adc_val >= config.ADC_TRIGGER_THRESHOLD
if (adc_val - last_adc_val) > 200 and enable_check: and adc_val < peak_adc_val
and last_adc_val >= peak_adc_val):
# 封顶后下降沿触发:peak是最大值,当前值开始下降,且上次值还在peak位置
hardware_manager.start_idle_timer() # 重新计时 hardware_manager.start_idle_timer() # 重新计时
diff_ms = current_time - last_adc_trigger
if diff_ms < 3000:
peak_adc_val = 0 # 去抖期间重置峰值
time.sleep_ms(5)
continue
last_adc_trigger = current_time last_adc_trigger = current_time
trigger_adc_val = adc_val # 记录触发时的气压 peak_adc_val = 0 # 触发后重置峰
last_adc_val = adc_val # 更新基准值,防止连续增量误触发 # 触发前先把缓存刷出来,避免波形被长耗时处理截断
enable_check = False
_flush_pressure_buf("before_trigger") _flush_pressure_buf("before_trigger")
try: try:
@@ -411,7 +415,7 @@ def cmd_str():
camera_manager.show(camera_manager.read_frame()) camera_manager.show(camera_manager.read_frame())
except Exception as e: except Exception as e:
pass pass
time.sleep_ms(1) time.sleep_ms(5)
last_adc_val = adc_val last_adc_val = adc_val
except Exception as e: except Exception as e:
Binary file not shown.
+1 -1
View File
@@ -1,7 +1,7 @@
[basic] [basic]
type = cvimodel type = cvimodel
model = model_285484.cvimodel model = model_317828.cvimodel
[extra] [extra]
model_type = yolov5 model_type = yolov5
+7 -114
View File
@@ -23,14 +23,6 @@ from logger_manager import logger_manager
from wifi import wifi_manager from wifi import wifi_manager
import subprocess import subprocess
# protobuf 支持
try:
import tcp_messages_pb2 as pb
_HAS_PROTO = True
except ImportError:
_HAS_PROTO = False
print("[NET] tcp_messages_pb2 not found, protobuf disabled")
def _wifi_tls_would_block(exc): def _wifi_tls_would_block(exc):
""" """
@@ -80,9 +72,6 @@ class NetworkManager:
self._raw_line_data = [] self._raw_line_data = []
self._manual_trigger_flag = False self._manual_trigger_flag = False
# protobuf 协议支持
self._use_proto = _HAS_PROTO # 默认启用 proto(如果可用)
# 限制并发命令线程数 # 限制并发命令线程数
self._cmd_thread_lock = threading.Lock() self._cmd_thread_lock = threading.Lock()
self._cmd_thread_count = 0 self._cmd_thread_count = 0
@@ -722,103 +711,6 @@ class NetworkManager:
"""线程安全地将消息加入队列(公共方法)""" """线程安全地将消息加入队列(公共方法)"""
self._enqueue((msg_type, data_dict), high) self._enqueue((msg_type, data_dict), high)
def _make_send_packet(self, msg_type, data_dict):
"""根据协议模式构造发送数据包"""
if self._use_proto and _HAS_PROTO:
return self._make_proto_packet(msg_type, data_dict)
return self._netcore.make_packet(msg_type, data_dict)
def _make_proto_packet(self, msg_type, data_dict):
"""使用 protobuf 序列化构造数据包"""
try:
if msg_type == 1:
# 登录消息
msg = pb.LoginRequest(
device_id=data_dict.get("deviceId", ""),
password=data_dict.get("password", ""),
if_admin=data_dict.get("ifAdmin", False),
version=data_dict.get("version", ""),
vol=data_dict.get("vol", 0),
vol_per=data_dict.get("vol_per", 0),
iccid=data_dict.get("iccid", ""),
)
elif msg_type == 4:
# 心跳消息
msg = pb.Heartbeat(
t=data_dict.get("t", 0),
vol=data_dict.get("vol", 0),
vol_per=data_dict.get("vol_per", 0),
)
elif msg_type == 2:
# 业务逻辑消息
cmd = data_dict.get("cmd", 0)
inner_data = {k: v for k, v in data_dict.items() if k != "cmd"}
data_bytes = json.dumps(inner_data).encode("utf-8") if inner_data else b""
msg = pb.LogicBody(cmd=cmd, data=data_bytes)
else:
# 其他消息类型,回退到 JSON
return self._netcore.make_packet(msg_type, data_dict)
body_bytes = msg.SerializeToString()
return self._netcore.make_packet_pb(msg_type, body_bytes)
except Exception as e:
self.logger.error(f"[NET] protobuf 序列化失败,回退到 JSON: {e}")
return self._netcore.make_packet(msg_type, data_dict)
def _parse_recv(self, payload):
"""解析接收的数据包,返回 (msg_type, body_dict)"""
if self._use_proto and _HAS_PROTO:
msg_type, body_bytes = self._netcore.parse_packet_raw(payload)
if msg_type is None:
return None, None
try:
body_dict = self._parse_proto_body(msg_type, body_bytes)
return msg_type, body_dict
except Exception as e:
self.logger.error(f"[NET] protobuf 反序列化失败: {e}")
# 回退到 JSON 解析
return self._netcore.parse_packet(payload)
else:
return self._netcore.parse_packet(payload)
def _parse_proto_body(self, msg_type, body_bytes):
"""将 protobuf body bytes 反序列化为 dict"""
if msg_type == 1:
msg = pb.LoginResponse()
msg.ParseFromString(body_bytes)
return {"cmd": msg.cmd, "data": msg.data}
elif msg_type == 4:
# 心跳 ACK 通常无 body
return {}
elif msg_type == 2:
msg = pb.LogicBody()
msg.ParseFromString(body_bytes)
result = {"cmd": msg.cmd}
if msg.data:
try:
result["data"] = json.loads(msg.data.decode("utf-8"))
except:
result["data"] = {"raw": msg.data.hex()}
return result
elif msg_type == 40:
msg = pb.OtaFragment()
msg.ParseFromString(body_bytes)
return {"l": msg.l, "d": msg.d, "t": msg.t, "v": msg.v}
elif msg_type == 100:
msg = pb.ImageUploadCommand()
msg.ParseFromString(body_bytes)
return {"uploadUrl": msg.upload_url, "token": msg.token, "shootId": msg.shoot_id, "outlink": msg.outlink}
elif msg_type == 101:
msg = pb.LogUploadCommand()
msg.ParseFromString(body_bytes)
return {"uploadUrl": msg.upload_url, "token": msg.token, "key": msg.key, "outlink": msg.outlink, "archive": msg.archive}
else:
# 未知类型,尝试 JSON 解析
try:
return json.loads(body_bytes.decode("utf-8"))
except:
return {"raw": body_bytes.hex()}
def connect_server(self): def connect_server(self):
""" """
连接到服务器(自动选择WiFi或4G) 连接到服务器(自动选择WiFi或4G)
@@ -1950,13 +1842,14 @@ class NetworkManager:
login_data = { login_data = {
"deviceId": self.device_id, "deviceId": self.device_id,
"password": self.password, "password": self.password,
"version": config.APP_VERSION + ("+proto" if self._use_proto else ""), "version": config.APP_VERSION,
"vol": vol_val, "vol": vol_val,
"vol_per": voltage_to_percent(vol_val) "vol_per": voltage_to_percent(vol_val)
} }
iccid_pending_marker = self._maybe_add_iccid_to_login(login_data) iccid_pending_marker = self._maybe_add_iccid_to_login(login_data)
print(f"login_data: {login_data}") print(f"login_data: {login_data}")
if not self.tcp_send_raw(self._make_send_packet(1, login_data)): # if not self.tcp_send_raw(self.make_packet(1, login_data)):
if not self.tcp_send_raw(self._netcore.make_packet(1, login_data)):
self._tcp_connected = False self._tcp_connected = False
try: try:
self.disconnect_server() self.disconnect_server()
@@ -2033,7 +1926,7 @@ class NetworkManager:
pass pass
# msg_type, body = self.parse_packet(payload) # msg_type, body = self.parse_packet(payload)
msg_type, body = self._parse_recv(payload) msg_type, body = self._netcore.parse_packet(payload)
# 处理登录响应 # 处理登录响应
if not logged_in and msg_type == 1: if not logged_in and msg_type == 1:
@@ -2417,7 +2310,7 @@ class NetworkManager:
if item: if item:
msg_type, data_dict = item msg_type, data_dict = item
pkt = self._make_send_packet(msg_type, data_dict) pkt = self._netcore.make_packet(msg_type, data_dict)
if not self.tcp_send_raw(pkt): if not self.tcp_send_raw(pkt):
# 发送失败:将消息放回队首(队列满则丢弃) # 发送失败:将消息放回队首(队列满则丢弃)
with self.get_queue_lock(): with self.get_queue_lock():
@@ -2446,8 +2339,8 @@ class NetworkManager:
current_time = time.ticks_ms() current_time = time.ticks_ms()
if logged_in and current_time - last_heartbeat_send_time > config.HEARTBEAT_INTERVAL * 1000: if logged_in and current_time - last_heartbeat_send_time > config.HEARTBEAT_INTERVAL * 1000:
vol_val = get_bus_voltage() vol_val = get_bus_voltage()
heartbeat_pkt = self._make_send_packet(4, {"vol": vol_val, "vol_per": voltage_to_percent(vol_val)}) if not self.tcp_send_raw(
if not self.tcp_send_raw(heartbeat_pkt): self._netcore.make_packet(4, {"vol": vol_val, "vol_per": voltage_to_percent(vol_val)})):
# if not self.tcp_send_raw(self.make_packet(4, {"vol": vol_val, "vol_per": voltage_to_percent(vol_val)})): # if not self.tcp_send_raw(self.make_packet(4, {"vol": vol_val, "vol_per": voltage_to_percent(vol_val)})):
send_hartbeat_fail_count += 1 send_hartbeat_fail_count += 1
# 短暂波动可能导致一次发送失败:连续失败达到阈值才重连,避免重连风暴 # 短暂波动可能导致一次发送失败:连续失败达到阈值才重连,避免重连风暴
-295
View File
@@ -1,295 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: tcp_messages.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12tcp_messages.proto\x12\x03tcp\"\x83\x01\n\x0cLoginRequest\x12\x11\n\tdevice_id\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x10\n\x08if_admin\x18\x03 \x01(\x08\x12\x0f\n\x07version\x18\x04 \x01(\t\x12\x0b\n\x03vol\x18\x05 \x01(\x01\x12\x0f\n\x07vol_per\x18\x06 \x01(\x01\x12\r\n\x05iccid\x18\x07 \x01(\t\"*\n\rLoginResponse\x12\x0b\n\x03\x63md\x18\x01 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\t\"4\n\tHeartbeat\x12\t\n\x01t\x18\x01 \x01(\x03\x12\x0b\n\x03vol\x18\x02 \x01(\x01\x12\x0f\n\x07vol_per\x18\x03 \x01(\x01\"&\n\tLogicBody\x12\x0b\n\x03\x63md\x18\x01 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\")\n\x0cResponseBody\x12\x0b\n\x03\x63md\x18\x01 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"@\n\x0bMonitorBody\x12\x11\n\tdevice_id\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x10\n\x08msg_type\x18\x03 \x01(\r\",\n\x16MonitorDevicesResponse\x12\x12\n\ndevice_ids\x18\x01 \x03(\t\"9\n\x0bOtaFragment\x12\t\n\x01l\x18\x01 \x01(\x05\x12\t\n\x01\x64\x18\x02 \x01(\t\x12\t\n\x01t\x18\x03 \x01(\x05\x12\t\n\x01v\x18\x04 \x01(\t\"\xab\x02\n\tShootData\x12\x0f\n\x07shot_id\x18\x01 \x01(\t\x12\t\n\x01x\x18\x02 \x01(\x01\x12\t\n\x01y\x18\x03 \x01(\x01\x12\t\n\x01r\x18\x04 \x01(\x01\x12\t\n\x01\x64\x18\x05 \x01(\x01\x12\x0b\n\x03\x61\x64\x63\x18\x06 \x01(\x01\x12\x14\n\x0ctarget_class\x18\x07 \x01(\t\x12\x1f\n\x17target_class_confidence\x18\x08 \x01(\x01\x12\x0f\n\x07\x64_laser\x18\t \x01(\x01\x12\x17\n\x0f\x64_laser_quality\x18\n \x01(\x01\x12\t\n\x01m\x18\x0b \x01(\t\x12\x14\n\x0claser_method\x18\x0c \x01(\t\x12\x10\n\x08target_x\x18\r \x01(\x01\x12\x10\n\x08target_y\x18\x0e \x01(\x01\x12\x15\n\roffset_method\x18\x0f \x01(\t\x12\x17\n\x0f\x64istance_method\x18\x10 \x01(\t\"!\n\nShootEvent\x12\x13\n\x0bshoot_event\x18\x01 \x01(\t\"C\n\rBatteryReport\x12\x0f\n\x07\x62\x61ttery\x18\x01 \x01(\x01\x12\x0f\n\x07voltage\x18\x02 \x01(\x01\x12\x10\n\x08net_type\x18\x03 \x01(\t\"9\n\x11\x43\x65nterPointResult\x12\x0e\n\x06result\x18\x01 \x01(\t\x12\t\n\x01x\x18\x02 \x01(\x01\x12\t\n\x01y\x18\x03 \x01(\x01\"3\n\x0e\x43\x65nterPointSet\x12\x0b\n\x03\x63md\x18\x01 \x01(\r\x12\t\n\x01x\x18\x02 \x01(\x01\x12\t\n\x01y\x18\x03 \x01(\x01\"(\n\tOtaResult\x12\x0e\n\x06result\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\"\x1f\n\rGenericResult\x12\x0e\n\x06result\x18\x01 \x01(\t\"&\n\x08IpReport\x12\x0e\n\x06result\x18\x01 \x01(\t\x12\n\n\x02ip\x18\x02 \x01(\t\"Z\n\x12ImageUploadCommand\x12\x12\n\nupload_url\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\t\x12\x10\n\x08shoot_id\x18\x03 \x01(\t\x12\x0f\n\x07outlink\x18\x04 \x01(\t\"d\n\x10LogUploadCommand\x12\x12\n\nupload_url\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\t\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x0f\n\x07outlink\x18\x04 \x01(\t\x12\x0f\n\x07\x61rchive\x18\x05 \x01(\t\"K\n\x0eOtaRequestData\x12\x0c\n\x04ssid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x0b\n\x03url\x18\x03 \x01(\t\x12\x0c\n\x04mode\x18\x04 \x01(\t\"1\n\x0fWifiConnectData\x12\x0c\n\x04ssid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\"_\n\x11ImageUploadResult\x12\x0e\n\x06result\x18\x01 \x01(\t\x12\x10\n\x08shoot_id\x18\x02 \x01(\t\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x0e\n\x06reason\x18\x04 \x01(\t\x12\x0b\n\x03via\x18\x05 \x01(\t\"v\n\x0fLogUploadResult\x12\x0e\n\x06result\x18\x01 \x01(\t\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x10\n\x08\x66ilename\x18\x03 \x01(\t\x12\x13\n\x0bstatus_code\x18\x04 \x01(\x05\x12\x0c\n\x04ssid\x18\x05 \x01(\t\x12\x0e\n\x06\x64\x65tail\x18\x06 \x01(\t\"J\n\x14\x42\x61tteryQueryResponse\x12\x0f\n\x07\x62\x61ttery\x18\x01 \x01(\x01\x12\x0f\n\x07voltage\x18\x02 \x01(\x01\x12\x10\n\x08net_type\x18\x03 \x01(\t\"\'\n\x0fOta4gSubCodeReq\x12\t\n\x01l\x18\x01 \x01(\x05\x12\t\n\x01v\x18\x02 \x01(\t\"5\n\x10Ota4gSubCodeResp\x12\x0b\n\x03\x63md\x18\x01 \x01(\r\x12\t\n\x01l\x18\x02 \x01(\x05\x12\t\n\x01v\x18\x03 \x01(\t\"\x1e\n\x0fShutdownCommand\x12\x0b\n\x03\x63md\x18\x01 \x01(\r\" \n\x0c\x41utoShutdown\x12\x10\n\x08poweroff\x18\x01 \x01(\tBB\n\rcom.shoot.tcpZ1git.shelingxingqiu.com/shoot-tcp-server/proto;tcpb\x06proto3')
_LOGINREQUEST = DESCRIPTOR.message_types_by_name['LoginRequest']
_LOGINRESPONSE = DESCRIPTOR.message_types_by_name['LoginResponse']
_HEARTBEAT = DESCRIPTOR.message_types_by_name['Heartbeat']
_LOGICBODY = DESCRIPTOR.message_types_by_name['LogicBody']
_RESPONSEBODY = DESCRIPTOR.message_types_by_name['ResponseBody']
_MONITORBODY = DESCRIPTOR.message_types_by_name['MonitorBody']
_MONITORDEVICESRESPONSE = DESCRIPTOR.message_types_by_name['MonitorDevicesResponse']
_OTAFRAGMENT = DESCRIPTOR.message_types_by_name['OtaFragment']
_SHOOTDATA = DESCRIPTOR.message_types_by_name['ShootData']
_SHOOTEVENT = DESCRIPTOR.message_types_by_name['ShootEvent']
_BATTERYREPORT = DESCRIPTOR.message_types_by_name['BatteryReport']
_CENTERPOINTRESULT = DESCRIPTOR.message_types_by_name['CenterPointResult']
_CENTERPOINTSET = DESCRIPTOR.message_types_by_name['CenterPointSet']
_OTARESULT = DESCRIPTOR.message_types_by_name['OtaResult']
_GENERICRESULT = DESCRIPTOR.message_types_by_name['GenericResult']
_IPREPORT = DESCRIPTOR.message_types_by_name['IpReport']
_IMAGEUPLOADCOMMAND = DESCRIPTOR.message_types_by_name['ImageUploadCommand']
_LOGUPLOADCOMMAND = DESCRIPTOR.message_types_by_name['LogUploadCommand']
_OTAREQUESTDATA = DESCRIPTOR.message_types_by_name['OtaRequestData']
_WIFICONNECTDATA = DESCRIPTOR.message_types_by_name['WifiConnectData']
_IMAGEUPLOADRESULT = DESCRIPTOR.message_types_by_name['ImageUploadResult']
_LOGUPLOADRESULT = DESCRIPTOR.message_types_by_name['LogUploadResult']
_BATTERYQUERYRESPONSE = DESCRIPTOR.message_types_by_name['BatteryQueryResponse']
_OTA4GSUBCODEREQ = DESCRIPTOR.message_types_by_name['Ota4gSubCodeReq']
_OTA4GSUBCODERESP = DESCRIPTOR.message_types_by_name['Ota4gSubCodeResp']
_SHUTDOWNCOMMAND = DESCRIPTOR.message_types_by_name['ShutdownCommand']
_AUTOSHUTDOWN = DESCRIPTOR.message_types_by_name['AutoShutdown']
LoginRequest = _reflection.GeneratedProtocolMessageType('LoginRequest', (_message.Message,), {
'DESCRIPTOR' : _LOGINREQUEST,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.LoginRequest)
})
_sym_db.RegisterMessage(LoginRequest)
LoginResponse = _reflection.GeneratedProtocolMessageType('LoginResponse', (_message.Message,), {
'DESCRIPTOR' : _LOGINRESPONSE,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.LoginResponse)
})
_sym_db.RegisterMessage(LoginResponse)
Heartbeat = _reflection.GeneratedProtocolMessageType('Heartbeat', (_message.Message,), {
'DESCRIPTOR' : _HEARTBEAT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.Heartbeat)
})
_sym_db.RegisterMessage(Heartbeat)
LogicBody = _reflection.GeneratedProtocolMessageType('LogicBody', (_message.Message,), {
'DESCRIPTOR' : _LOGICBODY,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.LogicBody)
})
_sym_db.RegisterMessage(LogicBody)
ResponseBody = _reflection.GeneratedProtocolMessageType('ResponseBody', (_message.Message,), {
'DESCRIPTOR' : _RESPONSEBODY,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.ResponseBody)
})
_sym_db.RegisterMessage(ResponseBody)
MonitorBody = _reflection.GeneratedProtocolMessageType('MonitorBody', (_message.Message,), {
'DESCRIPTOR' : _MONITORBODY,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.MonitorBody)
})
_sym_db.RegisterMessage(MonitorBody)
MonitorDevicesResponse = _reflection.GeneratedProtocolMessageType('MonitorDevicesResponse', (_message.Message,), {
'DESCRIPTOR' : _MONITORDEVICESRESPONSE,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.MonitorDevicesResponse)
})
_sym_db.RegisterMessage(MonitorDevicesResponse)
OtaFragment = _reflection.GeneratedProtocolMessageType('OtaFragment', (_message.Message,), {
'DESCRIPTOR' : _OTAFRAGMENT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.OtaFragment)
})
_sym_db.RegisterMessage(OtaFragment)
ShootData = _reflection.GeneratedProtocolMessageType('ShootData', (_message.Message,), {
'DESCRIPTOR' : _SHOOTDATA,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.ShootData)
})
_sym_db.RegisterMessage(ShootData)
ShootEvent = _reflection.GeneratedProtocolMessageType('ShootEvent', (_message.Message,), {
'DESCRIPTOR' : _SHOOTEVENT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.ShootEvent)
})
_sym_db.RegisterMessage(ShootEvent)
BatteryReport = _reflection.GeneratedProtocolMessageType('BatteryReport', (_message.Message,), {
'DESCRIPTOR' : _BATTERYREPORT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.BatteryReport)
})
_sym_db.RegisterMessage(BatteryReport)
CenterPointResult = _reflection.GeneratedProtocolMessageType('CenterPointResult', (_message.Message,), {
'DESCRIPTOR' : _CENTERPOINTRESULT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.CenterPointResult)
})
_sym_db.RegisterMessage(CenterPointResult)
CenterPointSet = _reflection.GeneratedProtocolMessageType('CenterPointSet', (_message.Message,), {
'DESCRIPTOR' : _CENTERPOINTSET,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.CenterPointSet)
})
_sym_db.RegisterMessage(CenterPointSet)
OtaResult = _reflection.GeneratedProtocolMessageType('OtaResult', (_message.Message,), {
'DESCRIPTOR' : _OTARESULT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.OtaResult)
})
_sym_db.RegisterMessage(OtaResult)
GenericResult = _reflection.GeneratedProtocolMessageType('GenericResult', (_message.Message,), {
'DESCRIPTOR' : _GENERICRESULT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.GenericResult)
})
_sym_db.RegisterMessage(GenericResult)
IpReport = _reflection.GeneratedProtocolMessageType('IpReport', (_message.Message,), {
'DESCRIPTOR' : _IPREPORT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.IpReport)
})
_sym_db.RegisterMessage(IpReport)
ImageUploadCommand = _reflection.GeneratedProtocolMessageType('ImageUploadCommand', (_message.Message,), {
'DESCRIPTOR' : _IMAGEUPLOADCOMMAND,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.ImageUploadCommand)
})
_sym_db.RegisterMessage(ImageUploadCommand)
LogUploadCommand = _reflection.GeneratedProtocolMessageType('LogUploadCommand', (_message.Message,), {
'DESCRIPTOR' : _LOGUPLOADCOMMAND,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.LogUploadCommand)
})
_sym_db.RegisterMessage(LogUploadCommand)
OtaRequestData = _reflection.GeneratedProtocolMessageType('OtaRequestData', (_message.Message,), {
'DESCRIPTOR' : _OTAREQUESTDATA,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.OtaRequestData)
})
_sym_db.RegisterMessage(OtaRequestData)
WifiConnectData = _reflection.GeneratedProtocolMessageType('WifiConnectData', (_message.Message,), {
'DESCRIPTOR' : _WIFICONNECTDATA,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.WifiConnectData)
})
_sym_db.RegisterMessage(WifiConnectData)
ImageUploadResult = _reflection.GeneratedProtocolMessageType('ImageUploadResult', (_message.Message,), {
'DESCRIPTOR' : _IMAGEUPLOADRESULT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.ImageUploadResult)
})
_sym_db.RegisterMessage(ImageUploadResult)
LogUploadResult = _reflection.GeneratedProtocolMessageType('LogUploadResult', (_message.Message,), {
'DESCRIPTOR' : _LOGUPLOADRESULT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.LogUploadResult)
})
_sym_db.RegisterMessage(LogUploadResult)
BatteryQueryResponse = _reflection.GeneratedProtocolMessageType('BatteryQueryResponse', (_message.Message,), {
'DESCRIPTOR' : _BATTERYQUERYRESPONSE,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.BatteryQueryResponse)
})
_sym_db.RegisterMessage(BatteryQueryResponse)
Ota4gSubCodeReq = _reflection.GeneratedProtocolMessageType('Ota4gSubCodeReq', (_message.Message,), {
'DESCRIPTOR' : _OTA4GSUBCODEREQ,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.Ota4gSubCodeReq)
})
_sym_db.RegisterMessage(Ota4gSubCodeReq)
Ota4gSubCodeResp = _reflection.GeneratedProtocolMessageType('Ota4gSubCodeResp', (_message.Message,), {
'DESCRIPTOR' : _OTA4GSUBCODERESP,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.Ota4gSubCodeResp)
})
_sym_db.RegisterMessage(Ota4gSubCodeResp)
ShutdownCommand = _reflection.GeneratedProtocolMessageType('ShutdownCommand', (_message.Message,), {
'DESCRIPTOR' : _SHUTDOWNCOMMAND,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.ShutdownCommand)
})
_sym_db.RegisterMessage(ShutdownCommand)
AutoShutdown = _reflection.GeneratedProtocolMessageType('AutoShutdown', (_message.Message,), {
'DESCRIPTOR' : _AUTOSHUTDOWN,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.AutoShutdown)
})
_sym_db.RegisterMessage(AutoShutdown)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\rcom.shoot.tcpZ1git.shelingxingqiu.com/shoot-tcp-server/proto;tcp'
_LOGINREQUEST._serialized_start=28
_LOGINREQUEST._serialized_end=159
_LOGINRESPONSE._serialized_start=161
_LOGINRESPONSE._serialized_end=203
_HEARTBEAT._serialized_start=205
_HEARTBEAT._serialized_end=257
_LOGICBODY._serialized_start=259
_LOGICBODY._serialized_end=297
_RESPONSEBODY._serialized_start=299
_RESPONSEBODY._serialized_end=340
_MONITORBODY._serialized_start=342
_MONITORBODY._serialized_end=406
_MONITORDEVICESRESPONSE._serialized_start=408
_MONITORDEVICESRESPONSE._serialized_end=452
_OTAFRAGMENT._serialized_start=454
_OTAFRAGMENT._serialized_end=511
_SHOOTDATA._serialized_start=514
_SHOOTDATA._serialized_end=813
_SHOOTEVENT._serialized_start=815
_SHOOTEVENT._serialized_end=848
_BATTERYREPORT._serialized_start=850
_BATTERYREPORT._serialized_end=917
_CENTERPOINTRESULT._serialized_start=919
_CENTERPOINTRESULT._serialized_end=976
_CENTERPOINTSET._serialized_start=978
_CENTERPOINTSET._serialized_end=1029
_OTARESULT._serialized_start=1031
_OTARESULT._serialized_end=1071
_GENERICRESULT._serialized_start=1073
_GENERICRESULT._serialized_end=1104
_IPREPORT._serialized_start=1106
_IPREPORT._serialized_end=1144
_IMAGEUPLOADCOMMAND._serialized_start=1146
_IMAGEUPLOADCOMMAND._serialized_end=1236
_LOGUPLOADCOMMAND._serialized_start=1238
_LOGUPLOADCOMMAND._serialized_end=1338
_OTAREQUESTDATA._serialized_start=1340
_OTAREQUESTDATA._serialized_end=1415
_WIFICONNECTDATA._serialized_start=1417
_WIFICONNECTDATA._serialized_end=1466
_IMAGEUPLOADRESULT._serialized_start=1468
_IMAGEUPLOADRESULT._serialized_end=1563
_LOGUPLOADRESULT._serialized_start=1565
_LOGUPLOADRESULT._serialized_end=1683
_BATTERYQUERYRESPONSE._serialized_start=1685
_BATTERYQUERYRESPONSE._serialized_end=1759
_OTA4GSUBCODEREQ._serialized_start=1761
_OTA4GSUBCODEREQ._serialized_end=1800
_OTA4GSUBCODERESP._serialized_start=1802
_OTA4GSUBCODERESP._serialized_end=1855
_SHUTDOWNCOMMAND._serialized_start=1857
_SHUTDOWNCOMMAND._serialized_end=1887
_AUTOSHUTDOWN._serialized_start=1889
_AUTOSHUTDOWN._serialized_end=1921
# @@protoc_insertion_point(module_scope)
+2 -2
View File
@@ -3,8 +3,8 @@
from maix import app, gpio, pinmap, time from maix import app, gpio, pinmap, time
PIN = "A17" PIN = "P19"
GPIO_NAME = "GPIOA17" GPIO_NAME = "GPIOP19"
def main(): def main():
+1
View File
@@ -31,3 +31,4 @@
# 2.15.18 wifi连接成功重新登录 # 2.15.18 wifi连接成功重新登录
# 2.16.4 优化射箭延迟 # 2.16.4 优化射箭延迟
# 2.17.0 yolo标靶类别识别 # 2.17.0 yolo标靶类别识别
# 3.0.4 26-09-03 9:36 引脚修改:A23 -> P19 red light
+1 -1
View File
@@ -4,6 +4,6 @@
应用版本号 应用版本号
每次 OTA 更新时只需要更新这个文件中的版本号 每次 OTA 更新时只需要更新这个文件中的版本号
""" """
VERSION = '3.0.5' VERSION = '2.18.4'