Commit 46d0cba6 刘韬

新增/rest/api/v1/shelf/allConfigPosOn接口,优化现有posOn和posOff接口的日志记录与错误处理,更新版本号至1.73

1 个父辈 68b0632a
# SmartShelf 智能库位灯控系统
[![Python 3](https://img.shields.io/badge/Python-3.x-blue.svg)](https://www.python.org/)
[![Flask](https://img.shields.io/badge/Flask-1.0+-green.svg)](https://flask.palletsprojects.com/)
[![Platform](https://img.shields.io/badge/Platform-Raspberry%20Pi-red.svg)](https://www.raspberrypi.org/)
[![Version](https://img.shields.io/badge/Version-v1.73-orange.svg)](./app/version.txt)
## 项目简介
SmartShelf(智能料架灯控边缘服务)是一款运行在树莓派上的工业级库位灯控系统。该系统通过 HTTP API 与上位系统交互,控制 WS281x LED 灯条和 GPIO 三色灯塔灯,实现智能仓储中的库位指示、拣货引导、状态提示等功能。
### 核心功能
- **库位灯控**:通过 LED 灯条指示具体库位位置,支持多种颜色(红、绿、蓝、黄、白等)
- **灯塔状态灯**:A/B 两面各配置绿/黄/红三色 GPIO 灯塔灯,显示通道工作状态
- **闪烁提醒**:支持库位灯闪烁模式,用于紧急或特殊任务提示
- **上位系统对接**:支持主动轮询和被动 API 调用两种工作模式
- **配置热更新**:支持在线上传库位配置文件,无需重启服务
- **网络配置**:内置网络配置管理,支持 DHCP 和静态 IP
## 系统架构
```
┌─────────────────────────────────────────────────────────────┐
│ SmartShelf System │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ REST API │ │ HTTP API │ │ Web Console │ │
│ │ (v1) │ │ (/api/*) │ │ (Frontend) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
├─────────┼─────────────────┼─────────────────┼───────────────┤
│ │ │ │ │
│ ┌──────▼───────┐ ┌──────▼───────┐ ┌──────▼───────┐ │
│ │ Flask │ │ 业务逻辑层 │ │ 硬件抽象层 │ │
│ │ 路由层 │ │ (post.py) │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ┌────────────────────────────────────┼─────────┐ │
│ │ │ │ │
│ ┌──────▼───────┐ ┌────────────────┐ ┌────▼────────▼─┐ │
│ │ WS281x │ │ GPIO │ │ CSV 配置 │ │
│ │ LED 灯条 │ │ 三色灯塔灯 │ │ 文件 │ │
│ │ (2路通道) │ │ (6路输出) │ │ │ │
│ └──────────────┘ └────────────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## 目录结构
```
smartshelf/
├── app/
│ ├── __init__.py # Flask 应用初始化、日志配置、启动线程
│ ├── routes.py # HTTP 路由定义(灯控 API、配置接口)
│ ├── post.py # 上位系统通信、轮询线程、闪烁调度
│ ├── led_strip.py # WS281x LED 灯条驱动封装
│ ├── driver_gpio.py # GPIO 灯塔灯控制
│ ├── config_ip.py # 网络配置管理
│ ├── autoback.py # 自动备份与 OTA 升级
│ ├── qr_code.py # 库位二维码生成
│ ├── g.py # 全局配置对象(LED 颜色顺序等)
│ ├── templates/ # HTML 模板文件
│ │ ├── base.html
│ │ ├── ledtest.html # LED 测试页面
│ │ ├── shelfconfig.html # 货架配置页面
│ │ └── ipconfig.html # 网络配置页面
│ └── static/ # 静态资源
│ ├── index.html # Web 控制台主页
│ ├── assets/ # 前端构建资源
│ ├── css/ # Bootstrap 样式
│ ├── js/ # JavaScript 库
│ └── uploads/ # 上传文件目录(库位配置 CSV)
├── config.py # 应用配置类
├── neotelshelf.py # Flask Shell 上下文
├── docs/ # 文档目录
│ └── SMARTSHELF-API-AND-DOTNET8-MIGRATION.md
└── version.txt # 版本号
```
## 硬件要求
### 运行平台
- **树莓派**(Raspberry Pi 3/4 或更高版本)
- **操作系统**:Raspberry Pi OS (32/64-bit)
- **运行权限**:必须以 root 身份运行(WS281x DMA、GPIO 操作需要)
### 硬件接口
#### WS281x LED 灯条(2路)
| 逻辑通道 | BCM GPIO | 说明 |
|---------|---------|------|
| Channel 1 (A面) | GPIO 12 | 数据信号线 |
| Channel 2 (B面) | GPIO 21 | 数据信号线 |
- LED 数量:可配置,默认支持最多 1000 颗
- 信号频率:800 KHz
- 亮度:0-255 可配置
#### GPIO 三色灯塔灯(6路)
| 灯塔灯 | BCM GPIO | 说明 |
|-------|---------|------|
| greenA (A面绿) | GPIO 5 | 正常工作状态 |
| yellowA (A面黄) | GPIO 6 | 有任务状态(自动联动) |
| redA (A面红) | GPIO 16 | 异常/停止状态 |
| greenB (B面绿) | GPIO 17 | 正常工作状态 |
| yellowB (B面黄) | GPIO 27 | 有任务状态(自动联动) |
| redB (B面红) | GPIO 23 | 异常/停止状态 |
## 数据配置
### 库位配置文件 (`linePositions.csv`)
```csv
位置,优先级,高度,宽度,料仓ID,设备IP,区域ID,灯索引
S11695-02-035,1,100,50,1,192.168.1.100,1,0
S11695-02-036,2,100,50,1,192.168.1.100,1,1
S11695-02-101,1,100,50,1,192.168.1.100,2,0
S11695-02-102,2,100,50,1,192.168.1.100,2,1
```
**字段说明**
- **位置** (第0列):库位编码,唯一标识
- **区域ID** (第6列):灯条通道,1=Channel 1,2=Channel 2,3=双面
- **灯索引** (第7列):该库位在对应灯条上的像素下标(从0起)
### 服务器连接配置 (`state/ipconfig.csv`)
```csv
http://192.168.1.100:8080,CID001
```
- 第一列:服务器基址
- 第二列:设备 CID(客户端标识)
## API 接口文档
### 1. REST v1 接口(纯文本响应)
#### 库位亮灯
```
GET /rest/api/v1/shelf/posOn?posId=库位@颜色;库位@颜色
示例:
/rest/api/v1/shelf/posOn?posId=S11695-02-035@green;S11695-02-036@red
响应:posOn OK / posOn FAIL
```
#### 库位灭灯
```
GET /rest/api/v1/shelf/posOff?posId=库位;库位
示例:
/rest/api/v1/shelf/posOff?posId=S11695-02-035;S11695-02-036
响应:posOff OK / posOff FAIL
```
#### 全亮/全灭
```
GET /rest/api/v1/shelf/allPosOn # 所有灯白色
GET /rest/api/v1/shelf/allPosOff # 所有灯熄灭
GET /rest/api/v1/shelf/keepAlive # 心跳检测
```
#### 灯塔灯控制
```
GET /rest/api/v1/shelf/lightHouse?op=GA@ON;YA@OFF;RA@ON
灯码说明:
- GA/YA/RA = A面绿/黄/红
- GB/YB/RB = B面绿/黄/红
- 操作:ON/OFF
响应:OK / FAIL
```
### 2. JSON API 接口
#### 聚合开灯
```http
POST /api/open
Content-Type: application/json
{
"params": "statusa=green;5=red;statusb=yellow;10=blue"
}
响应:
{"status": 0, "msg": "success"} # 成功
{"status": 1, "msg": {"failed": "5=red"}} # 部分失败
```
**参数说明**
- `statusa/statusb`:控制 A/B 面状态灯
- `数字`:按 `/positionlist` 顺序的 1-based 索引
- `库位名=颜色`:直接指定库位和颜色
#### 聚合关灯
```http
POST /api/close
Content-Type: application/json
{
"params": "statusa;5;10"
}
```
### 3. 单库位控制接口
#### 开灯
```http
POST /ledopen
Content-Type: application/json
{
"light_led": "S11695-02-035",
"light_led_color": "green"
}
```
#### 关灯
```http
POST /ledoff
Content-Type: application/json
{
"off_led": "S11695-02-035"
}
```
#### 重置所有灯
```http
POST /resetled
```
### 4. 状态灯控制接口
#### 开状态灯
```http
POST /workinglight
Content-Type: application/json
{
"workchannel": "channel1", // channel1=A面, channel2=B面
"workcolor": "green" // green/yellow/red
}
```
#### 关状态灯
```http
POST /workingoff
Content-Type: application/json
{
"workchannel": "channel1",
"workcolor": "green"
}
```
### 5. 系统管理接口
#### 获取运行状态
```http
POST /getstate
响应:
[
{
"state": "on", // on/off
"msg": "未进行测试动作",
"ipconfig": {
"ip": "http://...",
"cid": "CID001",
"post": "success" // success/failed/wait
},
"version": "1.73"
}
]
```
#### 启动/停止轮询
```http
POST /startpost # 启动与上位系统的轮询通信
POST /stoppost # 停止轮询
```
#### 上传配置文件
```http
POST /upload/
Content-Type: multipart/form-data
file: linePositions.csv
响应:{"filename": "linePositions.csv"}
```
#### 获取库位列表
```http
GET /positionlist
响应:["S11695-02-035", "S11695-02-036", ...]
```
#### 获取配置状态
```http
GET /config_state
响应:true / false
```
#### 获取设备 MAC 地址
```http
GET /getmac
响应:B8:27:EB:XX:XX:XX
```
### 6. 网络配置接口(仅 Linux)
#### 获取网络信息
```http
POST /get_networkinfo
响应:
{
"interface": "eth0",
"ip_address": "192.168.1.100",
"netmask": "255.255.255.0",
"gateway": "192.168.1.1"
}
```
#### 配置网络
```http
POST /ip_config
Content-Type: application/x-www-form-urlencoded
# DHCP 模式
dhcp=on
# 静态 IP 模式
new_ip=192.168.1.100&new_mask=255.255.255.0&router_ip=192.168.1.1
响应:Apply Successfully, System will reboot in 5 seconds
```
## 上位系统通信协议
### 轮询机制
当启动轮询(`/startpost`)后,系统每秒向服务器发送 POST 请求:
```http
POST {ip}/service/store/communication
Content-Type: application/json
{
"boxStatus": {
"1": {
"boxId": 1,
"status": 1,
"msg": null,
"temperature": null,
"humidity": null,
"data": {}
}
},
"alarmList": [],
"cid": "CID001",
"seq": 1, // 递增序列号
"op": 0,
"data": {},
"status": 1,
"msg": null
}
```
### 服务器响应指令
服务器在响应的 `data` 字段中下发控制指令:
#### 开灯指令
```json
{
"data": {
"open": "库位=颜色|库位=颜色=闪烁时长|..."
}
}
示例:
{
"data": {
"open": "S11695-02-035=green|S11695-02-036=red=5000|S11695-02-037=blue"
}
}
```
**说明**
- 两段(库位=颜色):常亮
- 三段(库位=颜色=毫秒):闪烁指定时长,约每 333ms 翻转亮灭
#### 关灯指令
```json
{
"data": {
"close": "库位|库位|..."
}
}
```
#### 全关灯/全开灯
```json
{
"data": {
"closeAll": "任意值" // 关闭所有灯
}
}
{
"data": {
"openAll": "green" // 所有灯设为指定颜色
}
}
```
#### 灯塔灯控制
```json
{
"data": {
"lightHouseOpenOrClose": "open=A=green|close=B=red"
}
}
```
## 安装与部署
### 1. 环境准备
```bash
# 更新系统
sudo apt-get update
sudo apt-get upgrade -y
# 安装 Python 3 及依赖
sudo apt-get install python3 python3-pip -y
# 安装系统依赖(WS281x 需要)
sudo apt-get install python3-dev swig -y
```
### 2. 安装 Python 依赖
```bash
# 进入项目目录
cd /prog/smartshelf
# 安装依赖
pip3 install flask flask-babel requests chardet qrcode pillow matplotlib rpi_ws281x
```
### 3. 配置启动
#### 使用 systemd 服务
创建服务文件 `/etc/systemd/system/smartshelf.service`
```ini
[Unit]
Description=SmartShelf LED Control Service
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/prog/smartshelf
Environment="PYTHONPATH=/prog/smartshelf"
ExecStart=/usr/bin/python3 /prog/smartshelf/neotelshelf.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
启用服务:
```bash
sudo systemctl daemon-reload
sudo systemctl enable smartshelf
sudo systemctl start smartshelf
```
#### 查看日志
```bash
# 查看服务日志
sudo journalctl -u smartshelf -f
# 查看应用日志
tail -f /prog/smartshelf/logs/smart.log
```
### 4. 首次配置
1. 上传库位配置文件:
```bash
curl -X POST -F "file=@linePositions.csv" http://192.168.1.100:5000/upload/
```
2. 配置服务器地址:
```bash
curl -X POST http://192.168.1.100:5000/updateip \
-H "Content-Type: application/json" \
-d '{"ip": "http://192.168.1.200:8080", "cid": "CID001"}'
```
3. 启动轮询:
```bash
curl -X POST http://192.168.1.100:5000/startpost
```
## 开发与调试
### 本地开发(非树莓派环境)
在非树莓派环境下运行时,系统会自动进入模拟模式:
- WS281x 操作仅打印日志
- GPIO 操作仅打印日志
- 无需 root 权限
### Web 控制台
启动后访问 `http://<树莓派IP>:5000/` 进入 Web 控制台,可:
- 可视化测试 LED 灯条
- 查看和管理库位配置
- 配置网络参数
- 查看日志和系统状态
### 颜色支持
系统支持以下颜色名称(不区分大小写):
- 基础色:`red`, `green`, `blue`, `yellow`, `white`, `off`
- 扩展色:`orange`, `cyan`, `magenta`, `purple`, `pink`
- 更多:`lightblue`, `skyblue`, `forestgreen`, `darkgreen`, `firebrick`, `indianred`
- 十六进制:`#RRGGBB` 格式
## 配置参数
### 货架配置(`g.py`)
| 参数 | 类型 | 默认值 | 说明 |
|-----|------|-------|------|
| `LED_ColorSort` | int | 2 (RGB) | LED 颜色顺序:1=GRB, 2=RGB |
| `LED_COUNT` | int | 1000 | 单条灯带最大像素数 |
| `TOWER_CUSTCONTROL` | int | 0 | 0=自动联动状态灯,1=手动控制 |
### 应用配置(`config.py`)
| 参数 | 默认值 | 说明 |
|-----|-------|------|
| `URL` | `http://192.168.1.100:8080/service/store/communication` | 服务器通信地址 |
| `DEFAULT_COLOR` | `green` | 默认亮灯颜色 |
| `LOG_LEVEL` | `logging.WARNING` | 日志级别 |
| `LOG_FOLDER` | `logs/smart.log` | 日志文件路径 |
## 注意事项
1. **权限要求**:必须以 root 身份运行,否则 WS281x 和 GPIO 操作将失败
2. **硬件安全**:确保 LED 灯条供电充足,避免电源不足导致闪烁异常
3. **网络配置**:首次部署需要配置网络参数,建议使用静态 IP
4. **配置文件**:CSV 文件编码会自动检测,建议统一使用 UTF-8 或 GBK
5. **并发访问**:灯条操作在多线程环境下是安全的,但 API 调用过多可能导致闪烁
## 故障排查
### 常见问题
| 问题 | 可能原因 | 解决方法 |
|-----|---------|---------|
| 灯条不亮 | GPIO 未初始化/电源问题 | 检查 GPIO 12/21 连接,确认 5V 电源供电充足 |
| 灯条颜色异常 | 颜色顺序配置错误 | 检查 `LED_ColorSort` 配置,尝试切换 RGB/GRB |
| 无法连接服务器 | IP/CID 配置错误 | 使用 `/getstate` 检查配置,重新调用 `/updateip` |
| 配置文件加载失败 | CSV 格式/编码问题 | 检查文件编码,确保表头正确 |
| GPIO 操作失败 | 非 root 权限运行 | 使用 sudo 或修改 systemd 服务配置 |
### 日志诊断
```bash
# 实时查看日志
tail -f /prog/smartshelf/logs/smart.log
# 查看最近日志
curl -X POST http://192.168.1.100:5000/taillog
```
## 版本历史
| 版本 | 日期 | 主要更新 |
|-----|------|---------|
| v1.73 | 2024 | 当前版本,支持库位灯闪烁控制 |
## 许可证
本项目为内部工业控制系统,仅供授权用户使用。
## 技术支持
- 项目维护:智能仓储系统团队
- 问题反馈:通过 TeamViewer 远程协助(`/openteamview` 接口)
---
**注意**:本文档基于代码分析生成,具体部署请参考现场实施文档和实际环境配置。
...@@ -721,6 +721,38 @@ def rest_api_v1_shelf_allPosOn(): ...@@ -721,6 +721,38 @@ def rest_api_v1_shelf_allPosOn():
logging.error(f'[allPosOn] error: {e}') logging.error(f'[allPosOn] error: {e}')
return 'allPosOn FAIL' return 'allPosOn FAIL'
#/rest/api/v1/shelf/allConfigPosOn
#allConfigPosOn OK 或 allConfigPosOn FAIL
@app.route('/rest/api/v1/shelf/allConfigPosOn', methods=['Get'])
def rest_api_v1_shelf_allConfigPosOn():
"""
仅按库位配置点亮所有库位灯(不会点亮非库位灯)。
"""
client_ip = request.remote_addr
logging.info(f'[allConfigPosOn] start turning on configured position lights, from {client_ip}')
try:
strip1 = get_strip(SET_LED_CHANNEL['1'])
strip2 = get_strip(SET_LED_CHANNEL['2'])
turned_on = 0
for posname, cfg in config_dict.items():
try:
led_index = int(cfg.split('@')[0])
channel = cfg.split('@')[1]
strip = strip1 if channel == '1' else strip2
strip.setPixelColor(led_index, Color(255,255,255))
turned_on += 1
except Exception as one_err:
logging.warning(f'[allConfigPosOn] skip invalid config pos="{posname}", cfg="{cfg}", err={one_err}')
strip1.show()
strip2.show()
logging.info(f'[allConfigPosOn] finished, configured positions on={turned_on}')
return 'allConfigPosOn OK'
except Exception as e:
logging.error(f'[allConfigPosOn] error: {e}')
return 'allConfigPosOn FAIL'
#/rest/api/v1/shelf/allPosOff #/rest/api/v1/shelf/allPosOff
#allPosOff OK allPosOff FAIL #allPosOff OK allPosOff FAIL
@app.route('/rest/api/v1/shelf/allPosOff', methods=['Get']) @app.route('/rest/api/v1/shelf/allPosOff', methods=['Get'])
......
1.72
\ No newline at end of file \ No newline at end of file
1.73
\ No newline at end of file \ No newline at end of file
# SmartShelf 接口、功能与 .NET 8 迁移概要
本文档描述 **SmartShelf(料架/库位灯控边缘服务)** 的 HTTP 接口、核心业务与硬件/数据契约,便于重做 **.NET 8** 版本时对齐行为。(不含 Web 前端、SPA、模板页面。)
---
## 1. 项目定位
设备侧服务承担三类事:
1. **解析库位表**:从 CSV 建立「库位编码 → 灯条通道 + 像素序号」的映射。
2. **驱动硬件**:两路 WS281x 灯条;六路 GPIO 数字输出驱动 A/B 两面各绿/黄/红三色「灯塔」状态灯。
3. **与上位交互**:HTTP API 直连;并可按需启动后台线程,以固定周期 **POST** `{服务器基址}/service/store/communication`,根据响应 JSON 里的 `data` 批量改灯、控塔灯、闪烁定时等。
**运行身份(写死)**:本服务在树莓派上 **必须以 root 身份运行**(例如 systemd `User=root`,或直接 `sudo` 启动)。不要求也不支持以普通用户 + udev/capability 等折中方式作为正式部署路径——WS281x DMA、GPIO 及若干运维接口均按 **root 可用** 为前提设计。
---
## 2. HTTP 接口清单
### 2.1 配置与库位列表
| 方法 | 路径 | 响应 / 行为 |
|------|------|----------------|
| GET | `/config_state` | JSON:`true` / `false`(库位 CSV 是否成功加载) |
| GET | `/positionlist` | JSON 数组:全部库位编码(与 CSV 第一列一致,不含表头) |
| POST | `/upload/` | 上传文件到约定目录后重新加载库位表 |
| POST | `/setconfig` | JSON 与运行时 **货架配置** 同名字段合并后持久化 |
**货架配置**(默认值可被持久化覆盖):
| 字段 | 含义 |
|------|------|
| `LED_ColorSort` | 灯条颜色分量顺序:`RGB``GRB`(与 `rpi_ws281x` 的 Color 构造一致) |
| `LED_COUNT` | 单条灯带逻辑像素个数(初始化 PixelStrip 时使用) |
| `TOWER_CUSTCONTROL` | 非 0 时:**关闭**「运行中有库位亮灯则自动点亮该侧黄塔灯」「启动自检里自动拉绿色塔灯」等默认塔灯联动 |
### 2.2 状态灯 / GPIO(逻辑面:channel1 = A,channel2 = B)
| 方法 | 路径 | Body(JSON) |
|------|------|----------------|
| POST | `/workinglight` | `workchannel``workcolor``green` \| `yellow` \| `red` → 对应 GPIO 置高 |
| POST | `/workingoff` | 同上 → 置低 |
### 2.3 单库位 WS281x(库位名须与 `/positionlist` 中一致)
| 方法 | 路径 | Body(JSON) |
|------|------|----------------|
| POST | `/ledopen` | `light_led``light_led_color` |
| POST | `/ledoff` | `off_led` |
| POST | `/resetled` | 两路灯条全黑并清空内部状态缓存 |
### 2.4 整条灯条测试
| 方法 | 路径 | Body(JSON) |
|------|------|----------------|
| POST | `/lineledon` | `channel_num``channel1` / `channel2``channel_color` |
| POST | `/lineledoff` | 同上,整条条灭 |
| POST | `/opAll` | `op``on`(现实现几乎不拉亮条,仅返回)/ `off`(两路全黑) |
### 2.5 聚合 JSON API
| 方法 | 路径 | Body | 响应 |
|------|------|------|------|
| POST | `/api/open` | `params`:分号分隔项,如 `statusa=green;5=red;statusb=yellow` | `status:0` 成功;`status:1` 且带 `failed` 字符串 |
| POST | `/api/close` | 同上风格;可含仅数字的灭灯项、`statusa` / `statusb` 关多色等 | 同上 |
说明:`statusa` / `statusb`**塔灯 GPIO**;纯数字 key 表示 **`/positionlist` 顺序下的 1-based 索引**(不是 CSV 行号);越界项进入失败列表。
### 2.6 REST v1(GET,响应为纯文本)
| 方法 | 路径 | 查询 | 成功 / 失败文本 |
|------|------|------|------------------|
| GET | `/rest/api/v1/shelf/posOn` | `posId=库位@颜色;…` | `posOn OK` / `posOn FAIL` |
| GET | `/rest/api/v1/shelf/posOff` | `posId=库位;…` | `posOff OK` / `posOff FAIL` |
| GET | `/rest/api/v1/shelf/allPosOn` | — | `allPosOn OK` / `FAIL` |
| GET | `/rest/api/v1/shelf/allPosOff` | — | `allPosOff OK` / `FAIL` |
| GET | `/rest/api/v1/shelf/keepAlive` | — | `OK` |
| GET | `/rest/api/v1/shelf/lightHouse` | `op=灯码@ON\|OFF;…` | `OK` / `FAIL` |
`lightHouse` 灯码与 GPIO 对应关系见下文 **§3.3**
### 2.7 设备与其它
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/getmac` | 优先 `eth*` 网卡 MAC,大写 |
| GET | `/download/` | 下载运行日志文件(部署路径因环境而异) |
### 2.8 上游轮询与运维状态
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/startpost` | 在内存中运行状态非 `on` 且已配置服务器地址时:置运行状态为 `on`,启动**定时 POST 上游****闪烁调度****不写磁盘**) |
| POST | `/stoppost` | 内存中置运行状态为 `off``is_start=false`,复位灯与轮询相关内存态 |
| POST | `/getstate` | 返回字段 **`state`****当前进程内存**中的运行状态(`on`/`off`);另含与服务器通信摘要、本地版本号等 |
| POST | `/taillog` | 日志尾部 + 最近 POST 成败摘要 |
| POST | `/updateip` | JSON:`ip``cid` → 写本地配置;原实现可顺带改 kiosk 自启动 |
### 2.9 网络(Linux)
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/ip_config` | form:DHCP 或静态 IP / 网关 / 掩码,写网络配置后延时重启 |
| POST | `/get_networkinfo` | 返回 `eth0` 的 IP、掩码、网关 JSON |
### 2.10 升级与其它
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/upgrade` | 比对远程 `version.txt`,下载 zip 覆盖应用目录后重启 |
| POST | `/openteamview` | 启动 TeamViewer(运维) |
### 2.11 二维码
| 方法 | 路径 | 说明 |
|------|------|------|
| GET / POST | `/downqrcode/?code=<编码>` | 生成带文案的合成标贴图并下载 |
---
## 3. 核心功能说明
### 3.1 库位表文件与表头结构
- **文件名**`linePositions.csv`(通常位于应用下 `static/uploads/`)。
- **编码**:读取时做编码探测,失败时常用 **GBK** 回退。
仓库内示例表头(**第 1 行为表头,加载时跳过**:第一列值为 `位置` 的行不参与映射):
| 列索引 | 列名 | 加载时是否参与灯控映射 |
|--------|------|------------------------|
| 0 | `位置` | **是** — 作为库位主键(字符串,如 `S11695-02-035`) |
| 1 | `优先级` | 否(业务展示/排序,灯控未用) |
| 2 | `高度` | 否 |
| 3 | `宽度` | 否 |
| 4 | `料仓ID` | 否 |
| 5 | `设备IP` | 否 |
| 6 | `区域ID` | **是** — 在内部表示 **灯条侧别/通道**,字符串形式 **`'1'`、`'2'`、`'3'`** |
| 7 | `灯索引` | **是** — 该库位在对应灯条上的 **像素下标**(整数,从 0 起) |
**内部映射构造规则**(与现逻辑一致即可):
- 对每一数据行:`config[行[位置]] = 行[灯索引] + "@" + 行[区域ID]`
例:`0@1` 表示区域 `1` 那路灯条上像素 `0`
- **`区域ID` 语义**
- `'1'`:只驱动 **GPIO 12** 那一路 WS281x。
- `'2'`:只驱动 **GPIO 21** 那一路。
- `'3'`**双面** — 同一库位同时在两路上占用 **相同像素下标**(开灯/关灯/闪烁应对两路各写一次)。
`/positionlist` 返回的是 **`config` 的 key 按 CSV 出现顺序** 的列表;`/api/open` 用的数字索引是 **该列表的 1-based 下标**
### 3.2 WS281x 灯条(两路)
| 逻辑通道(区域ID) | 数据线 BCM GPIO | 说明 |
|-------------------|-----------------|------|
| `1` | **12** | 常称 channel1 / A 面条 |
| `2` | **21** | 常称 channel2 / B 面条 |
**驱动库默认参数**(与现有 Python 初始化一致便于对时序):
| 参数 | 典型值 |
|------|--------|
| 信号频率 | 800 kHz |
| DMA | 10 |
| 信号反相 | 否 |
| 亮度 | 0–255 内可配置,默认约 100 |
| PWM 通道号 | 0 |
**像素数量**:运行时由货架配置 `LED_COUNT` 决定(默认量级可达千级,需与真实灯珠数一致)。
**颜色顺序**`LED_ColorSort` 为 RGB 时,`Color(R,G,B)` 按标准传参;为 GRB 时底层需交换 R/G 再送进控制器(与 WS2812 常见布线一致)。
**命名颜色 → RGB**(HTTP/API 颜色名不分大小写;`#RRGGBB` 十六进制也支持):
| 名称 | R,G,B |
|------|--------|
| red | 255,0,0 |
| green | 0,255,0 |
| blue | 0,0,255 |
| yellow | 255,255,0 |
| white | 255,255,255 |
| off / 灭灯 | 0,0,0 |
| orange | 255,97,0 |
| cyan | 0,255,255 |
| magenta | 255,0,255 |
| purple | 128,0,128 |
| pink | 255,192,203 |
| lightblue | 173,216,230 |
| skyblue | 135,206,235 |
| forestgreen | 34,139,34 |
| darkgreen | 0,100,0 |
| firebrick | 178,34,34 |
| indianred | 205,92,92 |
| 其它未识别名 | 白色 255,255,255 |
REST `allPosOn`**所有像素** 设为白色 `(255,255,255)`
### 3.3 GPIO 三色灯塔(数字输出,BCM 编号)
同一套引脚被 **`/workinglight`****/workingoff**、聚合 API 的 `statusa/statusb`**`/rest/.../lightHouse`**、以及上游 **`lightHouseOpenOrClose`****`towerLightControl`** 共用。
**默认映射表**(A/B 为物理面;高电平一般为「灯亮」,依硬件接法确认):
| 逻辑键 | 含义 | BCM GPIO |
|--------|------|----------|
| greenA | A 面 绿 | **5** |
| yellowA | A 面 黄 | **6** |
| redA | A 面 红 | **16** |
| greenB | B 面 绿 | **17** |
| yellowB | B 面 黄 | **27** |
| redB | B 面 红 | **23** |
**REST `lightHouse` 查询参数 `op` 中的灯码**(不区分大小写)与上表对应:
| 灯码 | 逻辑键 | GPIO |
|------|--------|------|
| GA | greenA | 5 |
| YA | yellowA | 6 |
| RA | redA | 16 |
| GB | greenB | 17 |
| YB | yellowB | 27 |
| RB | redB | 23 |
每段格式:`灯码@ON``灯码@OFF`,多段用 **`;`** 分隔。任一段格式错或灯码未知 → 整体可返回 `FAIL`
**备注**:代码里曾保留一套 **A/B 面对掉** 的注释映射(绿黄红引脚整组交换),若现场接线不同,.NET 版应做成**可配置表**,而非写死。
### 3.4 HTTP 业务与内部状态
- **`/ledopen` / `/ledoff`**:除改像素外,还在内存字典里维护「当前点亮集合」,便于一条上多库位累加点亮后再 `show()`
- **`/resetled`**:两路各清全带颜色为黑,并清空上述字典。
- **`/api/open` 与 `/api/close`**:直接操作 strip 缓冲区并 `show()`;与上面的「测试用字典」非同一套状态,并行调用时以最后 `show()` 为准。
- **并发**:灯条与 GPIO 均被多线程(HTTP + 轮询 + 闪烁)触碰,迁移时应对 **每条 strip 或全局灯控加互斥**,避免交错 `show()`
### 3.5 上游轮询:`/service/store/communication`
- **请求**:POST JSON,`Content-Type: application/json`。Body 含 `cid`、递增 `seq``op``status` 等;业务方主要关心返回体中的 **`data`** 对象。
- **典型 `data` 字段与语义**
| 字段 | 格式 | 行为概要 |
|------|------|----------|
| `open` | 多段用 `\|` 分隔,每段 `库位=颜色``库位=颜色=毫秒` | 两段:常亮该色;**三段**:第三段为 **闪烁总时长(毫秒)**,进入闪烁表,约每 **333ms** 翻转亮灭,倒计时结束灭灯并移出 |
| `close` | `\|` 分隔;每段至少含库位 | 按库位关像素,并从内存占灯表移除该项 |
| `closeAll` | 任意真值 | 两路清屏(逻辑等同全灭) |
| `openAll` | 颜色名 | 两路所有像素设为该颜色 |
| `lightHouseOpenOrClose` | `\|` 分隔多段;每段 **`open=侧=颜色`****`close=侧=颜色`**(恰好两个 `=` 拆成三段) | 侧:`A`/`B`;颜色:`red`/`green`/`yellow``open` → GPIO 高,`close` → 低(非 green/yellow 时按 red 处理) |
库位名字符串须在 **库位表** 中存在;否则记日志并跳过。
**独占塔灯行为**(当 **未** 开启 `TOWER_CUSTCONTROL` 时):
- 轮询路径维护「每通道是否还有亮着的库位像素」。
- **A 面(通道 `1`)** 有任意库位亮 → **yellowA (GPIO 6)** 拉高;全灭 → 拉低。
- **B 面(通道 `2`)** 同理 → **yellowB (GPIO 27)**
- 开启 `TOWER_CUSTCONTROL` 后,上述自动黄灯与 **启动自检末尾拉 greenA/greenB** 可被跳过,仅保留显式塔灯指令。
**启动自检**(轮询线程开始时,未开 `TOWER_CUSTCONTROL`):每条灯带依次整带红 → 绿 → 蓝 → 灭;然后 **greenA(5)、greenB(17)** 置高。
### 3.6 闪烁实现要点
-`(通道, led_index)` 在闪烁结构体中保存:颜色名、剩余毫秒数、`state` 翻转。
- 调度周期 **约 333ms**:每次递减剩余时间;若仍大于 0,则翻转亮/灭并在条上写当前色或 `off`;若 ≤0,写灭并从表删除。
### 3.7 持久化与路径(部署约定)
| 用途 | 常见相对/绝对路径 |
|------|-------------------|
| 库位 CSV | `…/static/uploads/linePositions.csv` |
| 服务器 `ip` + `cid` | `…/state/ipconfig.csv`(列:`ip`,`cid`) |
| 轮询运行状态 | **仅存进程内存**`on`/`off``is_start` 等);**不使用 `state.txt`**,进程退出即丢失,重启后需再次 `startpost` 或依赖 §3.9 自拉起逻辑 |
| 货架配置 | 原 `shelfconfig.pkl`,.NET 可改为 JSON |
| 设备版本 | `version.txt` |
| 日志 | 如 `logs/smart.log`(轮转) |
### 3.8 进程启动:加载顺序与一次性初始化
下列顺序描述 **宿主进程已启动、即将/正在构建 WebHost** 阶段应完成的工作(与现 Python 实现等价即可,不必逐文件同名)。
| 阶段 | 内容 |
|------|------|
| **1. 日志** | 配置日志级别(如 `WARNING`);挂 **滚动文件** Handler(单文件大小上限、保留个数),路径如 `logs/smart.log`。 |
| **2. 全局配置类** | 读入 `SECRET_KEY`、上传目录、state 目录、`LOG_*``DEFAULT_COLOR``TOWER_CUSTCONTROL` 等(可来自环境变量 + 默认值)。 |
| **3. 货架硬件配置** | 从持久化文件加载 **`LED_ColorSort`、`LED_COUNT`、`TOWER_CUSTCONTROL`**;若无文件则用默认(如 `LED_COUNT=1000`,颜色顺序 RGB)。 |
| **4. 库位表** | 读取 `linePositions.csv`(chardet + 回退编码);跳过表头行(首列 `位置`);填充内存映射 `config_dict` / `option_list`,并置 **`config_state`**。失败则 `config_state=false`,接口仍应答但库位相关操作会失败或打日志。 |
| **5. 服务器连接参数** | 读 `state/ipconfig.csv` 首行两列 → 内存中的 `ip``cid``post` 状态可先为 `wait`。文件缺失或读失败 → `ip` 为空并打日志(**不得**在未处理异常时崩溃整个进程)。 |
| **6. 注册路由 / Minimal APIs** | 挂载 §2 全部 HTTP 端点。 |
| **7. 惰性硬件** | **WS281x `get_strip`/初始化**:首次需要点灯时再创建(按 PIN 12 / 21 与 `LED_COUNT`);避免在无任何 CSV 时强行占 DMA。 |
| **8. 轮询全局标志** | 内存中 **`is_start = false`**,以及对外 API 使用的 **运行状态字符串**`on`/`off`,与 `startpost`/`stoppost`/`getstate` 一致);**均不持久化**。 |
**`version.txt`**
-**`/getstate`** 等读取本地版本号一行文本;若不存在可返回空字符串或默认,避免抛错。
- **与旧版 Python 的差异**:旧实现另用 `state.txt` 持久化轮询开关;**本迁移目标不再读写 `state.txt`**,仅以内存为准。
**与 Python 的差异说明**:源码末尾注释块里「import 后直接起 `serverpost`**未启用**;真实「上电后自动连服务器」依赖下面 **§3.9** 的 HTTP 自调用线程。
### 3.9 启动后:监听就绪后的自动线程(原 `__init__` 行为)
Web 服务 **开始监听** 之后,应启动(或调度)一条 **后台线程**,逻辑等价于:
1. **等待约 2 秒**(给 Kestrel/反向代理就绪时间)。
2. **POST** `http://127.0.0.1:<端口>/stoppost`(body 可为空或非 JSON,现实现不校验)。
3. **再等待约 2 秒**
4. **POST** `http://127.0.0.1:<端口>/startpost`
5. 打日志「启动完成」类信息。
**语义**:进程每次拉起时 **先停轮询再启轮询**,等价于强制走一遍「关闭成功 / 启动成功」状态机,使**内存中**运行状态最终为 `on` 且(在 `ip` 非空时)开始 **§3.10** 的双线程。
**迁移注意**
- 端口须与 Kestrel 实际监听一致(现网常 **5000**)。
- 若 2 秒内服务尚未监听,`stoppost` 会失败;.NET 可改为 **重试****HostedService 在 `IHostApplicationLifetime.ApplicationStarted` 之后再执行**,比固定 `sleep(2)` 稳妥。
- `stoppost` 会执行 **灯条黄闪复位、`post_leds` 清空、`is_start=false`、内存运行状态 `off`** 等;**`startpost`** 在运行状态已为 `on` 时不会重复起线程,但自动线程先 `stoppost` 会把状态拉回 `off``startpost`,因此 **每次冷启动仍会重新创建轮询与闪烁线程**
### 3.10 运行期:`startpost` 成功后的双循环
**`POST /startpost`** 判定可启动时(**内存**中运行状态非 `on`**`ip` 非空**):
- 成功后:**内存**运行状态置 **`on`**`is_start=true`,启动下列两线程;**不写任何「状态」文件**
-`ip` 为空或已在运行:不启线程,返回文案与现网 JSON 形状一致即可。
| 线程 / 任务 | 行为 |
|-------------|------|
| **`serverpost`** | ① 执行 **启动自检** `start_show`(§3.5 灯条 RGB 扫描 + 未禁塔控时 greenA/greenB)。② 为通道 `1``2``get_strip` 填入 `strips`。③ `seq=1`**`while is_start`**:调用 **出站 POST** `communication`(§3.4);若有返回且 `data` 非空则 **`resolve_data`****`check_leds()`**(自动黄灯);`seq++`**`sleep(1)`**。 |
| **`blinkLedProcess`** | 用 **`threading.Timer(0.333, …)` 链式重入** 或等价周期任务:在 **`blink_lock`** 下遍历 `blink_led`,对每个条目 **`waittimecount -= 333`**,未到 0 则翻转亮灭并写像素、`strip.show()`;到 0 则灭灯并删除。仅当 **`is_start`** 仍为 true 时注册下一次 333ms。 |
**`POST /stoppost`**:内存运行状态置 `off`**不写 `state.txt`**),`is_start=false`**`reset_strip()`**(每路黄 wipe → 黑 wipe,清空 `post_leds`),`post` 状态置 `wait`,并依赖 **`check_leds()`** 关自动黄灯。
**与 HTTP 灯控 API 的关系**
- **`/ledopen`、`/ledoff`、`/api/*`、`/rest/*`** 等多数直接操作 strip,与 **`post_leds` / `blink_led`** 不一定同步(现实现存在多套内存状态)。**.NET 可统一为单一「灯态」权威源**;若需行为级兼容,需按端点分别对照 Python 侧字典与 strip 写入顺序。
**全局变量 `is_start`**:与 `app` 模块同级;**仅** `startpost` / `stoppost` / `serverpost` / `blinkLedProcess` 读写;HTTP 其它路径不直接改。
### 3.11 运行期:配置热更新
| 事件 | 行为 |
|------|------|
| **`POST /upload/`** 成功 | 重新执行与 §3.8 第 4 步相同的 CSV 解析,**替换** `config_dict` / `option_list` / `config_state`。注意:现 Python 热加载**可能把 CSV 表头行误写入映射**,.NET 建议始终跳过表头。 |
| **`POST /setconfig`** | 合并货架配置并 **Save****已创建的 WS281x 条带**`LED_COUNT` 变更,通常需 **释放旧条并重建** 才与配置一致。 |
---
## 4. .NET 8 迁移注意点
| 方面 | 建议 |
|------|------|
| API | 路径、方法、JSON 键、REST 纯文本返回值与现网一致,避免多套上位机改版。 |
| 启动与后台任务 | **§3.8–§3.11**:加载顺序、监听后 `stoppost``startpost` 自调用、`serverpost`/`blink` 双循环及热更新;实现 `IHostedService` 时对齐该语义。 |
| 运行状态 | **仅内存**:不创建、不读取 **`state.txt`**`/getstate``state` 与内部 `is_start` 一致维护;**重启后默认视为 `off`**,直至 `startpost` 或 §3.9 自拉起成功。 |
| 运行身份 | **必须 root**(见 §1);启动时若检测到非 root 可直接拒绝启动或打日志告警(按产品约定)。 |
| 灯条 | WS281x 与原生 `rpi_ws281x` 类库在 **root** 下使用;若拆成独立控灯子进程,该子进程同样 **必须 root**。 |
| GPIO | 使用 `System.Device.Gpio` 等时,**BCM 号与上表对齐**;布线变种用配置覆盖。 |
| 配置 | `/prog/...``/home/pi`、重启脚本、日志路径全部外置配置。 |
| 线程安全 | 像素缓冲与 `show()`、GPIO 写建议单线程或锁串行化。 |
| 安全 | 内网 + 鉴权;当前接口可无认证改网络与 OTA。**root 进程暴露 HTTP 时务必仅绑定内网或防火墙隔离。** |
---
## 5. 用本文档直接生成树莓派 .NET 8 程序:尚缺或可裁定的信息
下列项在正文中**未写死或仅部分覆盖**,实现前需要补齐约定或现场参数,否则无法「一键生成」可部署二进制。
### 5.1 运行与部署
| 缺口 | 说明 |
|------|------|
| **监听 URL** | 现 Flask 侧常见为 **`http://*:5000`**(仓库自测脚本指向 `127.0.0.1:5000`)。Kestrel 需约定:`http://0.0.0.0:5000` 是否与现网一致,或改用 reverse proxy + 其它端口。 |
| **HTTP 状态码** | 多数接口不论成败常返回 **200**,错误信息在 JSON/纯文本里。与 ASP.NET 默认 `ProblemDetails` 习惯不同,需刻意对齐。 |
| **HTTPS / 证书** | 未涉及;若内网 HTTP 即可需写明。 |
### 5.2 WS281x 在 .NET 上的实现方式(文档未指定技术路线)
| 缺口 | 说明 |
|------|------|
| **无官方等价 `rpi_ws281x`** | .NET 8 需自选其一:**P/Invoke `libws2811`****调用现有 Python/C 小服务****单独进程 + IPC**、或第三方绑定。文档只描述行为,**不包含绑定的 C API/库版本/初始化顺序**。 |
| **权限** | 本文档约定:**必须以 root 运行**,不依赖 udev 细粒度授权作为正式方案。部署文档中写明 systemd **`User=root`**(或等价)即可。 |
| **与 `LED_COUNT` 不一致** | 物理灯珠少于 `LED_COUNT` 时行为依赖驱动;超标可能损坏显示逻辑,需现场确认或读配置。 |
### 5.3 上游 `communication` 协议(正文仅有字段语义)
| 缺口 | 说明 |
|------|------|
| **出站 POST 完整 JSON** | 现实现每次发送大致为:`boxStatus`(含占位 `data`)、`alarmList``cid``seq`(递增整数)、`op`(0)、`data`(空对象)、`status``msg` 等。**正文未逐字段列出**,迁移时应从现网抓包或与后端确认是否可增加字段。 |
| **超时** | 客户端 **2s** 超时;是否沿用需在配置中体现。 |
| **响应解析** | 代码使用 `response.json()`,并判断 `ele_json['data']`。若服务端把业务包在字符串里或键名变化,需额外契约。**`data` 与 `data.open` 等内层类型**(对象 vs 嵌套 JSON 字符串)应向后端确认。 |
| **无 `http` 前缀的 `ip`** | 现逻辑进入**本地演示**分支(内置假 `open` 指令跑一次),.NET 需决定是否保留该兼容性。 |
### 5.4 文件与数据格式细节
| 缺口 | 说明 |
|------|------|
| **`ipconfig.csv`** | **无表头**;第一行为两列:`ip``cid`(与 `csv.writer` 写出字段名一致,但文件内不写表头行)。`ip`**基址**(例如 `http://host:8080`),代码再拼 `/service/store/communication`。 |
| **`state.txt`** | **本迁移目标不采用**;轮询开关仅存内存(见 §3.7、§4)。若与旧 Python 并存部署,可忽略其 `state.txt` 或删除以免混淆。 |
| **`/setconfig` 与 `LED_ColorSort`** | 持久化里为 **整数**`GRB = 1``RGB = 2`(默认 RGB)。若 API 用字符串 `"RGB"`,需在服务内与整数互转。 |
| **`/upload/`** | `multipart/form-data` 字段名 **`file`**;成功返回形如 `{"filename":"..."}` 的字符串。GET 行为异常(返回 `True`),.NET 可只实现 POST。 |
| **`/reload` 与表头** | 首次加载跳过 `位置==位置`**热加载 `reload_config` 在 Python 里未再跳过表头**,可能把表头行写进映射——.NET 建议**始终跳过表头行**,不必与 bug 对齐。 |
| **`version.txt` / OTA** | 本地版本为**纯文本一行**。但 **`GET {ip}/download/version.txt` 的响应体被 `response.json()` 解析**,即服务端应对该 URL 返回 **JSON**(数字或带引号的字符串等形式),不是任意纯文本。ZIP 名为 **`{version}.zip`**,解压后文件拷贝规则依赖现场目录(原 `/prog/smartshelf/...`)。 |
### 5.5 若干接口的精确契约(正文未展开)
| 接口 | 缺口 |
|------|------|
| **`/getstate`** | 响应为 **JSON 数组**,单元素对象,键含:`state``msg``ipconfig``version``ipconfig` 内含运行时 `ip``cid`、最近 POST 结果 **`post`**(如 `success`/`failed`/`wait`)。 |
| **`/startpost`** | `state` 已为 `on` 时仍返回成功_msg「正在运行」;**`ip` 为空**时返回失败文案且 `state` 仍为 `off`。 |
| **`/api/close`** | `statusa`/`statusb` **无 `=`** 时表示关该侧**全部三色**;有 `=` 时只关指定颜色。仅数字项灭灯时,**通道为 `3`(双面)** 的库位在现 Python 里只更新了其中一条 strip,属**潜在缺陷**;.NET 可_fix 或与现网行为对齐需业务裁定。 |
| **`/download/`** | 固定读部署路径下 `smart.log`;无文件时**无明确响应**(.NET 应定义 404 或空)。 |
| **`/ip_config`** | `application/x-www-form-urlencoded`:字段含 **`dhcp`**(有则 DHCP)、**`new_ip`****`router_ip`****`new_mask`**(静态时);成功返回重启提示字符串,依赖 Linux 写 `/etc/dhcpcd.conf` 或 NetworkManager。 |
| **二维码 / TeamViewer / `openteamview`** | 依赖本机字体路径、TeamViewer 安装路径、root 密码交互(pexpect)等,**不可仅凭本文档还原安全模型**,需单独产品决策或标为「不移植」。 |
### 5.6 构建目标
| 缺口 | 说明 |
|------|------|
| **RID** | 树莓派 64 位 OS:`linux-arm64`;32 位则 `linux-arm`。是否 **self-contained**、是否 AOT,影响发布体积与依赖 `glibc`。 |
### 5.7 建议的最小补齐物(生成代码前)
1. 定稿 **Kestrel 监听地址与端口**、是否 **systemd** 托管(**`User=root`** 与 §1 一致)。
2. 选定 **WS281x 技术路线**(见 §5.2);**root 为硬性要求**,无需再评估「仅 sudo」与普通用户方案。
3. 向后端索取 **`/service/store/communication` 请求/响应 JSON Schema** 或真实样例(含 `data` 全类型)。
4. 确认 **OTA**`version.txt`**JSON 形态** 与 zip 内目录布局。
5. 列表:**不移植** 接口(`ip_config``upgrade``openteamview``downqrcode` 等)由项目经理划掉,减少范围。
---
*整理自现有实现与仓库内示例 CSV;现场若改表头或 GPIO 接线,以交付 configuration 为准。*
...@@ -589,83 +589,188 @@ def apiClose(): ...@@ -589,83 +589,188 @@ def apiClose():
#posOn OK 或 posOn FAIL #posOn OK 或 posOn FAIL
@app.route('/rest/api/v1/shelf/posOn', methods=['Get']) @app.route('/rest/api/v1/shelf/posOn', methods=['Get'])
def rest_api_v1_shelf_posOn(): def rest_api_v1_shelf_posOn():
"""
亮灯接口:
- 请求参数: posId=库位条码@颜色;库位条码@颜色
- 返回: 'posOn OK' / 'posOn FAIL'
"""
client_ip = request.remote_addr
posId = request.args.get('posId', '').strip()
logging.info(f'[posOn] request from {client_ip}, raw posId="{posId}"')
if not posId:
logging.warning('[posOn] missing posId parameter')
return 'posOn FAIL'
strip1 = get_strip(SET_LED_CHANNEL['1']) strip1 = get_strip(SET_LED_CHANNEL['1'])
strip2 = get_strip(SET_LED_CHANNEL['2']) strip2 = get_strip(SET_LED_CHANNEL['2'])
posId = request.args.get('posId')
option_list = posId.split(';') option_list = [x for x in posId.split(';') if x]
success = True success = True
for ol in option_list: for ol in option_list:
ds = ol.split('@') try:
posname = ds[0] if '@' not in ol:
color = ds[1] logging.warning(f'[posOn] invalid format (missing @): "{ol}"')
x = config_dict.get(posname) success = False
if x is None:
success=False
continue continue
channel = config_dict.get(posname).split('@')[1]
led_index = int(config_dict.get(posname).split('@')[0]) posname, color = ol.split('@', 1)
s = strip1 posname = posname.strip()
color = color.strip()
cfg = config_dict.get(posname)
if cfg is None:
logging.warning(f'[posOn] position not found in config: "{posname}"')
success = False
continue
led_index = int(cfg.split('@')[0])
channel = cfg.split('@')[1]
strip = strip1
if channel == '2': if channel == '2':
s = strip2 strip = strip2
s.setPixelColor(led_index, setcolor(color))
strip.setPixelColor(led_index, setcolor(color))
logging.info(f'[posOn] turn on light pos="{posname}", color="{color}", channel={channel}, index={led_index}')
except Exception as e:
success = False
logging.error(f'[posOn] error processing "{ol}": {e}')
strip1.show() strip1.show()
strip2.show() strip2.show()
if success: if success:
logging.info('[posOn] finished successfully')
return 'posOn OK' return 'posOn OK'
else: else:
logging.warning('[posOn] finished with failure')
return 'posOn FAIL' return 'posOn FAIL'
#/rest/api/v1/shelf/posOff?posId=1_3_1;1_3_2;1_3_4 #/rest/api/v1/shelf/posOff?posId=1_3_1;1_3_2;1_3_4
#posOff OK 或 posOff FAIL #posOff OK 或 posOff FAIL
@app.route('/rest/api/v1/shelf/posOff', methods=['Get']) @app.route('/rest/api/v1/shelf/posOff', methods=['Get'])
def rest_api_v1_shelf_posOff(): def rest_api_v1_shelf_posOff():
"""
灭灯接口:
- 请求参数: posId=库位条码;库位条码
- 返回: 'posOff OK' / 'posOff FAIL'
"""
client_ip = request.remote_addr
posId = request.args.get('posId', '').strip()
logging.info(f'[posOff] request from {client_ip}, raw posId="{posId}"')
if not posId:
logging.warning('[posOff] missing posId parameter')
return 'posOff FAIL'
strip1 = get_strip(SET_LED_CHANNEL['1']) strip1 = get_strip(SET_LED_CHANNEL['1'])
strip2 = get_strip(SET_LED_CHANNEL['2']) strip2 = get_strip(SET_LED_CHANNEL['2'])
posId = request.args.get('posId')
option_list = posId.split(';') option_list = [x for x in posId.split(';') if x]
success = True success = True
for posname in option_list: for posname in option_list:
x = config_dict.get(posname) try:
if x is None: posname = posname.strip()
success=False cfg = config_dict.get(posname)
if cfg is None:
logging.warning(f'[posOff] position not found in config: "{posname}"')
success = False
continue continue
channel = config_dict.get(posname).split('@')[1]
led_index = int(config_dict.get(posname).split('@')[0]) led_index = int(cfg.split('@')[0])
s = strip1 channel = cfg.split('@')[1]
strip = strip1
if channel == '2': if channel == '2':
s = strip2 strip = strip2
s.setPixelColor(led_index, Color(0,0,0))
strip.setPixelColor(led_index, Color(0,0,0))
logging.info(f'[posOff] turn off light pos="{posname}", channel={channel}, index={led_index}')
except Exception as e:
success = False
logging.error(f'[posOff] error processing "{posname}": {e}')
strip1.show() strip1.show()
strip2.show() strip2.show()
if success: if success:
logging.info('[posOff] finished successfully')
return 'posOff OK' return 'posOff OK'
else: else:
logging.warning('[posOff] finished with failure')
return 'posOff FAIL' return 'posOff FAIL'
#/rest/api/v1/shelf/allPosOn #/rest/api/v1/shelf/allPosOn
#allPosOn OK 或 allPosOn FAIL #allPosOn OK 或 allPosOn FAIL
@app.route('/rest/api/v1/shelf/allPosOn', methods=['Get']) @app.route('/rest/api/v1/shelf/allPosOn', methods=['Get'])
def rest_api_v1_shelf_allPosOn(): def rest_api_v1_shelf_allPosOn():
logging.info('开始打开所有灯') client_ip = request.remote_addr
for pin in [SET_LED_CHANNEL['1'],SET_LED_CHANNEL['2']]: logging.info(f'[allPosOn] start turning on all lights, from {client_ip}')
try:
for pin in [SET_LED_CHANNEL['1'], SET_LED_CHANNEL['2']]:
strip = get_strip(pin) strip = get_strip(pin)
for i in range(0,strip.numPixels()): for i in range(0, strip.numPixels()):
strip.setPixelColor(i, Color(255,255,255)) strip.setPixelColor(i, Color(255,255,255))
strip.show() strip.show()
logging.info('完成打开所有灯') logging.info('[allPosOn] finished turning on all lights')
return 'allPosOn OK' return 'allPosOn OK'
except Exception as e:
logging.error(f'[allPosOn] error: {e}')
return 'allPosOn FAIL'
#/rest/api/v1/shelf/allConfigPosOn
#allConfigPosOn OK 或 allConfigPosOn FAIL
@app.route('/rest/api/v1/shelf/allConfigPosOn', methods=['Get'])
def rest_api_v1_shelf_allConfigPosOn():
"""
仅按库位配置点亮所有库位灯(不会点亮非库位灯)。
"""
client_ip = request.remote_addr
logging.info(f'[allConfigPosOn] start turning on configured position lights, from {client_ip}')
try:
strip1 = get_strip(SET_LED_CHANNEL['1'])
strip2 = get_strip(SET_LED_CHANNEL['2'])
turned_on = 0
for posname, cfg in config_dict.items():
try:
led_index = int(cfg.split('@')[0])
channel = cfg.split('@')[1]
strip = strip1 if channel == '1' else strip2
strip.setPixelColor(led_index, Color(255,255,255))
turned_on += 1
except Exception as one_err:
logging.warning(f'[allConfigPosOn] skip invalid config pos="{posname}", cfg="{cfg}", err={one_err}')
strip1.show()
strip2.show()
logging.info(f'[allConfigPosOn] finished, configured positions on={turned_on}')
return 'allConfigPosOn OK'
except Exception as e:
logging.error(f'[allConfigPosOn] error: {e}')
return 'allConfigPosOn FAIL'
#/rest/api/v1/shelf/allPosOff #/rest/api/v1/shelf/allPosOff
#allPosOff OK allPosOff FAIL #allPosOff OK allPosOff FAIL
@app.route('/rest/api/v1/shelf/allPosOff', methods=['Get']) @app.route('/rest/api/v1/shelf/allPosOff', methods=['Get'])
def rest_api_v1_shelf_allPosOff(): def rest_api_v1_shelf_allPosOff():
logging.info('开始关闭所有灯') client_ip = request.remote_addr
for pin in [SET_LED_CHANNEL['1'],SET_LED_CHANNEL['2']]: logging.info(f'[allPosOff] start turning off all lights, from {client_ip}')
try:
for pin in [SET_LED_CHANNEL['1'], SET_LED_CHANNEL['2']]:
strip = get_strip(pin) strip = get_strip(pin)
for i in range(0,strip.numPixels()): for i in range(0, strip.numPixels()):
strip.setPixelColor(i, Color(0,0,0)) strip.setPixelColor(i, Color(0,0,0))
strip.show() strip.show()
logging.info('完成关闭所有灯') logging.info('[allPosOff] finished turning off all lights')
return 'allPosOff OK' return 'allPosOff OK'
except Exception as e:
logging.error(f'[allPosOff] error: {e}')
return 'allPosOff FAIL'
@app.route('/rest/api/v1/shelf/keepAlive') @app.route('/rest/api/v1/shelf/keepAlive')
def rest_api_v1_shelf_keepAlive(): def rest_api_v1_shelf_keepAlive():
return 'OK' return 'OK'
......
1.72
\ No newline at end of file \ No newline at end of file
1.73
\ No newline at end of file \ No newline at end of file
此文件太大,无法显示。
支持 Markdown 格式
你添加了 0 到此讨论。请谨慎行事。
Finish editing this message first!