feat: expose motor API on all interfaces
This commit is contained in:
parent
0dea53cabb
commit
bb8a69e2d0
@ -19,7 +19,8 @@ MOTOR_CLOSE_CONTROL_RAW=400
|
||||
# HTTP open/close 后持续广播直到 stop;服务正常退出会自动 Stop+Disable。
|
||||
MOTOR_BROADCAST_FREQUENCY_HZ=10
|
||||
|
||||
MOTOR_HTTP_HOST=127.0.0.1
|
||||
# 监听全部 IPv4 网络接口;客户端应使用本机真实局域网 IP,不能使用 0.0.0.0。
|
||||
MOTOR_HTTP_HOST=0.0.0.0
|
||||
MOTOR_HTTP_PORT=8000
|
||||
# 绑定非本机地址时必须设置,请使用足够长的随机值。
|
||||
# 留空表示不启用 API Key,任何能访问该端口的电脑都可调用接口。
|
||||
MOTOR_API_TOKEN=
|
||||
|
||||
26
README.md
26
README.md
@ -123,6 +123,9 @@ MOTOR_IDS=1,2
|
||||
MOTOR_OPEN_CONTROL_RAW=400
|
||||
MOTOR_CLOSE_CONTROL_RAW=400
|
||||
MOTOR_BROADCAST_FREQUENCY_HZ=10
|
||||
MOTOR_HTTP_HOST=0.0.0.0
|
||||
MOTOR_HTTP_PORT=8000
|
||||
MOTOR_API_TOKEN=
|
||||
```
|
||||
|
||||
`MOTOR_OPEN_CONTROL_RAW` 和 `MOTOR_CLOSE_CONTROL_RAW` 是幅值;程序按电机 ID
|
||||
@ -149,8 +152,12 @@ uv run --locked lingkong-motor-service
|
||||
如果 `.env` 不存在,脚本会根据 `.env.example` 创建一份,完成硬件检查并设置
|
||||
`MOTOR_HARDWARE_ENABLED=true` 后再次运行。
|
||||
|
||||
Swagger 界面默认为 <http://127.0.0.1:8000/docs>。真实硬件上只能运行一个服务
|
||||
进程,不要使用 `--reload` 或多个 worker。
|
||||
服务默认监听 `0.0.0.0:8000`,即全部 IPv4 网络接口。本机可打开
|
||||
<http://127.0.0.1:8000/docs>,其他电脑应访问
|
||||
`http://<服务器真实局域网IP>:8000/docs`;`0.0.0.0` 只是监听地址,不能作为客户端目标。
|
||||
当前 `MOTOR_API_TOKEN` 留空,不启用 API Key,任何能访问 TCP 8000 端口的电脑都可调用
|
||||
电机接口。请只在可信局域网中使用,并用主机或网络防火墙限制可访问范围。
|
||||
真实硬件上只能运行一个服务进程,不要使用 `--reload` 或多个 worker。
|
||||
|
||||
## 5. 首次实机测试顺序
|
||||
|
||||
@ -216,12 +223,13 @@ uv run --locked python tests/test_can.py stop
|
||||
完成上述低值方向验证后,正常操作顺序如下:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/v1/motor/open
|
||||
SERVER_IP="<服务器真实局域网IP>"
|
||||
curl -X POST "http://${SERVER_IP}:8000/v1/motor/open"
|
||||
# 方向切换会自动执行零输出、Stop 和 20 ms 等待
|
||||
curl -X POST http://127.0.0.1:8000/v1/motor/close
|
||||
curl -X POST http://127.0.0.1:8000/v1/motor/open
|
||||
curl -X POST http://127.0.0.1:8000/v1/motor/stop
|
||||
curl http://127.0.0.1:8000/v1/motor/status
|
||||
curl -X POST "http://${SERVER_IP}:8000/v1/motor/close"
|
||||
curl -X POST "http://${SERVER_IP}:8000/v1/motor/open"
|
||||
curl -X POST "http://${SERVER_IP}:8000/v1/motor/stop"
|
||||
curl "http://${SERVER_IP}:8000/v1/motor/status"
|
||||
```
|
||||
|
||||
`MOTOR_HARDWARE_ENABLED=true` 时,启动过程会自动 Enable。HTTP 只保留
|
||||
@ -237,11 +245,11 @@ curl http://127.0.0.1:8000/v1/motor/status
|
||||
`stop`。单次 `stop` 会自动重复两轮停止序列,不需要客户端再调用两次。
|
||||
服务正常退出会自动执行两轮 Stop,再 Disable 并关闭 CAN 设备。
|
||||
|
||||
如果设置了 `MOTOR_API_TOKEN`,请求需携带:
|
||||
当前配置不需要 API Key。如果以后显式设置了 `MOTOR_API_TOKEN`,请求才需携带:
|
||||
|
||||
```bash
|
||||
curl -X POST -H "X-API-Key: your-token" \
|
||||
http://127.0.0.1:8000/v1/motor/open
|
||||
"http://${SERVER_IP}:8000/v1/motor/open"
|
||||
```
|
||||
|
||||
## 7. 验证边界
|
||||
|
||||
@ -56,6 +56,6 @@ export UV_PROJECT_ENVIRONMENT="${UV_PROJECT_ENVIRONMENT:-/tmp/lingkong-motor-ser
|
||||
echo "Synchronizing the locked environment in ${UV_PROJECT_ENVIRONMENT}..."
|
||||
uv sync --locked
|
||||
|
||||
echo "Starting Lingkong motor service on ${MOTOR_HTTP_HOST:-127.0.0.1}:${SERVICE_PORT}..."
|
||||
echo "Starting Lingkong motor service on ${MOTOR_HTTP_HOST:-0.0.0.0}:${SERVICE_PORT}..."
|
||||
echo "Motors auto-enable at startup; Ctrl-C performs Stop followed by Disable."
|
||||
exec uv run --locked lingkong-motor-service
|
||||
|
||||
@ -69,7 +69,7 @@ class Settings:
|
||||
open_control_raw: int = 400
|
||||
close_control_raw: int = 400
|
||||
broadcast_frequency_hz: float = 10.0
|
||||
http_host: str = "127.0.0.1"
|
||||
http_host: str = "0.0.0.0"
|
||||
http_port: int = 8000
|
||||
api_token: str | None = None
|
||||
|
||||
@ -85,7 +85,7 @@ class Settings:
|
||||
open_control_raw=_env_int("MOTOR_OPEN_CONTROL_RAW", 400),
|
||||
close_control_raw=_env_int("MOTOR_CLOSE_CONTROL_RAW", 400),
|
||||
broadcast_frequency_hz=_env_float("MOTOR_BROADCAST_FREQUENCY_HZ", 10.0),
|
||||
http_host=os.getenv("MOTOR_HTTP_HOST", "127.0.0.1").strip(),
|
||||
http_host=os.getenv("MOTOR_HTTP_HOST", "0.0.0.0").strip(),
|
||||
http_port=_env_int("MOTOR_HTTP_PORT", 8000),
|
||||
api_token=token,
|
||||
)
|
||||
@ -139,5 +139,3 @@ class Settings:
|
||||
raise ConfigError("MOTOR_HTTP_HOST must not be empty")
|
||||
if not 1 <= self.http_port <= 65_535:
|
||||
raise ConfigError("MOTOR_HTTP_PORT must be in 1..65535")
|
||||
if self.http_host not in {"127.0.0.1", "localhost", "::1"} and not self.api_token:
|
||||
raise ConfigError("MOTOR_API_TOKEN is required when HTTP is exposed beyond localhost")
|
||||
|
||||
@ -13,6 +13,8 @@ def test_defaults_match_connected_ms3008_installation() -> None:
|
||||
assert settings.open_control_raw == 400
|
||||
assert settings.close_control_raw == 400
|
||||
assert settings.broadcast_frequency_hz == 10
|
||||
assert settings.http_host == "0.0.0.0"
|
||||
assert settings.api_token is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field", ["open_control_raw", "close_control_raw"])
|
||||
@ -71,6 +73,5 @@ def test_broadcast_frequency_must_be_finite_and_bounded(frequency: float) -> Non
|
||||
Settings(broadcast_frequency_hz=frequency).validate()
|
||||
|
||||
|
||||
def test_non_loopback_http_requires_api_token() -> None:
|
||||
with pytest.raises(ConfigError, match="MOTOR_API_TOKEN"):
|
||||
Settings(http_host="0.0.0.0").validate()
|
||||
def test_external_http_binding_does_not_require_api_token() -> None:
|
||||
Settings(http_host="0.0.0.0", api_token=None).validate()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user