From 2ad0488b26c648f80b2d3d2c99b40e5c78318bf9 Mon Sep 17 00:00:00 2001 From: lhx Date: Tue, 9 Jun 2026 11:09:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=B5=8B=E9=87=8F=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E9=80=81=E6=89=8B=E6=9C=BA=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 75 ++++++++++++++++++++++++------------------ shouji/main.py | 89 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 574f576..0afeed0 100644 --- a/README.md +++ b/README.md @@ -82,19 +82,7 @@ send.py --type disconnect --port 58000 PC 端蓝牙 SPP 作为服务端,等待手机 APP 连接后,模拟 Trimble DiNi 03 水准仪向手机发送 M5 格式测量数据。 -### 工作原理 - -``` -手机APP ──蓝牙──▶ 蓝牙模块 ──SPP串口──▶ PC (本服务) - ▲ │ - │ ?0100 轮询 │ 测量数据 (M5) - └────────────────────────────────┘ -``` - -- 手机通过蓝牙连接到 PC 的蓝牙模块 -- 手机 APP 周期性发送 `?0100` 轮询水准仪 -- 本服务响应协议握手/控制码,并在收到轮询时发送排队测量数据 -- 手机似乎只接收不回复确认 +数据发送为**直接发送**(调用 API 时立即通过串口发出,不等手机轮询)。 ### 启动 @@ -103,7 +91,7 @@ cd shouji python main.py # 默认 0.0.0.0:58100 python main.py --port 8081 # 指定 HTTP 端口 python main.py --bt-port COM3 # 启动时自动连接蓝牙 COM 口 -``` +python main.py --level-url http://192.168.1.100:58000 # 指定水准仪服务地址 ### API 接口 @@ -111,21 +99,19 @@ python main.py --bt-port COM3 # 启动时自动连接蓝牙 COM |------|------|------| | `GET` | `/` | 服务状态 + COM 口列表 | | `GET/POST` | `/test` | 测试:未连接→COM列表;已连接→手机连接状态+统计 | -| `GET` | `/status` | 详细状态:手机连接、待发送数、收发字节数 | +| `GET` | `/status` | 详细状态:手机连接、收发字节数 | | `POST` | `/connect` | `{"port":"COM3"}` 打开蓝牙 COM 口 | | `POST` | `/disconnect` | 断开 | | `POST` | `/command` | `{"cmd":"..."}` 发送命令 | +| `POST` | `/measure-and-send` | 一键操作:触发水准仪测量→获取数据→发到手机 | ### send.py 调用示例 ```bash # 从上级目录执行 send.py --type test --port 58100 -send.py --type connect --data COM3 --port 58100 # 连接蓝牙 COM 口 -send.py --type command --data "send 0.89182 3.323" --port 58100 # 添加测量到队列 -send.py --type command --data "send 1.50000 5.000" --port 58100 # 再添加一条 -send.py --type command --data "force" --port 58100 # 强制发送 (不等轮询) -send.py --type command --data "clear" --port 58100 # 清空队列 +send.py --type connect --data COM3 --port 58100 # 连接蓝牙 COM 口 +send.py --type command --data "send 0.89182 3.323" --port 58100 # 直接发送测量数据 send.py --type disconnect --port 58100 ``` @@ -133,12 +119,26 @@ send.py --type disconnect --port 58100 | 命令 | 格式 | 说明 | |------|------|------| -| `send` | `send ` | 添加测量到发送队列 (R=标尺读数, HD=距离) | -| `force` | `force` | 强制立即发送队列中第一条 | -| `clear` | `clear` | 清空待发送队列 | +| `send` | `send ` | 直接发送测量数据到手机 (R=标尺读数, HD=距离) | | `disconnect` | `disconnect` | 断开蓝牙连接 | | 原始 | `hex:AABBCC` | 发送原始 hex 字节 | +### /measure-and-send — 一键测量+发送 + +```bash +# curl 调用 (send.py 暂不支持此接口,直接用 curl) +curl -X POST http://127.0.0.1:58100/measure-and-send \ + -H "Content-Type: application/json" \ + -d '{}' + +# 指定水准仪服务地址 +curl -X POST http://127.0.0.1:58100/measure-and-send \ + -H "Content-Type: application/json" \ + -d '{"level_url":"http://192.168.1.100:58000"}' +``` + +工作流:先向水准仪服务发 `FML` 触发测量 → 解析返回的 `staff_reading` + `distance` → 直接通过蓝牙发到手机。 + --- ## 4. 共享客户端 (`send.py`) @@ -178,21 +178,34 @@ python send.py --type command --data "force" ### 场景: 电脑读取水准仪数据 → 发送到手机 APP +**方式一:一键操作 (推荐)** + ```bash # 终端1: 启动水准仪服务 -cd shuizhunyi -python main.py --dini-port COM6 # 端口 58000 +cd shuizhunyi && python main.py --dini-port COM6 # 终端2: 启动手机通信服务 -cd shouji -python main.py --bt-port COM3 # 端口 58100 +cd shouji && python main.py --bt-port COM3 + +# 一键: 触发测量 + 发送到手机 +curl -X POST http://127.0.0.1:58100/measure-and-send \ + -H "Content-Type: application/json" -d '{}' +``` + +**方式二:分步操作** + +```bash +# 终端1: 启动水准仪服务 +cd shuizhunyi && python main.py --dini-port COM6 + +# 终端2: 启动手机通信服务 +cd shouji && python main.py --bt-port COM3 # 终端3: 操作 -python send.py --type command --data "FML" --port 58000 # 从水准仪获取测量 -# → 假设返回 staff_reading=0.89182, distance=3.323 +python send.py --type command --data "FML" --port 58000 # 触发水准仪测量 +# → 返回 staff_reading=0.89182, distance=3.323 -python send.py --type command --data "send 0.89182 3.323" --port 58100 # 排队发给手机 -# 手机APP发送 ?0100 轮询时自动收到数据 +python send.py --type command --data "send 0.89182 3.323" --port 58100 # 直接发到手机 ``` --- diff --git a/shouji/main.py b/shouji/main.py index 41cc2bf..7522a56 100644 --- a/shouji/main.py +++ b/shouji/main.py @@ -16,7 +16,8 @@ API 接口: POST /connect {port} 打开蓝牙 COM 口, 等待手机连接 POST /disconnect 断开 POST /command {cmd} 命令: send 发送测量 | disconnect 断开 - GET /status 手机连接状态 + 待发送队列 + GET /status 手机连接状态 + POST /measure-and-send 触发水准仪测量(FML)→获取数据→发到手机 (一键操作) + 待发送队列 协议说明: 手机→PC: 0x02F0... 握手 | ?0100 轮询 | 0x050B8D 控制码 | KENC 编码 @@ -29,6 +30,8 @@ import sys import time import datetime import hashlib +import json +import urllib.request import threading import asyncio import argparse @@ -46,6 +49,7 @@ from pydantic import BaseModel DEFAULT_HTTP_PORT = 58100 DEFAULT_BAUDRATE = 9600 +_config = {"level_url": "http://127.0.0.1:58000"} # 水准仪服务地址 # ── 协议常量 ── HANDSHAKE_PHONE = bytes([0x02, 0xF0, 0x00, 0x00, 0xDE, 0x03, 0x00, 0x07]) @@ -488,6 +492,10 @@ class CommandRequest(BaseModel): cmd: str +class MeasureAndSendRequest(BaseModel): + level_url: str = "" # 留空则使用默认地址 + + # ════════════════════════════════════════════════════════════════ @asynccontextmanager @@ -585,6 +593,80 @@ async def disconnect(): return await loop.run_in_executor(None, _do) +# ── POST /measure-and-send ── + +@app.post("/measure-and-send") +async def measure_and_send(req: MeasureAndSendRequest = MeasureAndSendRequest()): + """ + 一键操作: 触发水准仪测量(FML) → 拿到数据 → 发到手机蓝牙。 + + 请求体 (均可选): + { "level_url": "http://127.0.0.1:58000" } # 水准仪服务地址 + """ + level_url = req.level_url.strip() or _config["level_url"] + + def _do(): + # 1. 检查本服务蓝牙已连接 + if not svc.connected: + raise HTTPException(status_code=400, + detail="手机蓝牙未连接, 请先 POST /connect") + + # 2. 向水准仪服务发 FML 触发测量 + try: + fm_req = urllib.request.Request( + f"{level_url}/command", + data=json.dumps({"cmd": "FML"}).encode("utf-8"), + headers={"Content-Type": "application/json"}, + method="POST", + ) + with urllib.request.urlopen(fm_req, timeout=15) as resp: + fm_result = json.loads(resp.read().decode("utf-8")) + except urllib.error.URLError as e: + raise HTTPException( + status_code=502, + detail=f"无法连接到水准仪服务 {level_url}: {e.reason}" + ) + except Exception as e: + raise HTTPException( + status_code=502, + detail=f"调用水准仪服务失败: {e}" + ) + + # 3. 解析测量结果 + if fm_result.get("status") != "ok": + raise HTTPException( + status_code=502, + detail=f"水准仪测量失败: {fm_result.get('error_desc') or fm_result.get('raw_text') or '未知错误'}" + ) + + r = fm_result.get("staff_reading") + hd = fm_result.get("distance") + if r is None or hd is None: + raise HTTPException( + status_code=502, + detail=f"水准仪返回数据不完整: {json.dumps(fm_result, ensure_ascii=False)}" + ) + + # 4. 直接发到手机蓝牙 + ok = svc.send_measurement(r, hd) + + return { + "status": "ok" if ok else "send_error", + "measurement": { + "staff_reading": r, + "distance": hd, + "height_diff": fm_result.get("height_diff"), + "std_dev": fm_result.get("std_dev"), + "raw_text": fm_result.get("raw_text"), + }, + "level_service": level_url, + "phone_sent": ok, + } + + loop = asyncio.get_running_loop() + return await loop.run_in_executor(None, _do) + + # ── POST /command ── @app.post("/command") @@ -670,8 +752,13 @@ def main(): help=f"HTTP 服务端口 (默认 {DEFAULT_HTTP_PORT})") parser.add_argument("--bt-port", default=None, help="启动时自动连接的蓝牙 COM 口 (如 COM3)") + parser.add_argument("--level-url", default=_config["level_url"], + help=f"水准仪服务地址 (默认 {_config['level_url']})") args = parser.parse_args() + # 更新全局配置 + _config["level_url"] = args.level_url + # ── 打印可连接 COM 口 ── print_com_list()