Metadata-Version: 2.4
Name: passive_server
Version: 1.34.0
Summary: 作为服务端的设备
Author: WeiLiu
Author-email: 183074632@qq.com
Requires-Python: >=3.11,<3.13
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Dist: alembic (>=1.14.0,<2.0.0)
Requires-Dist: bar-tender-api (>=1.0.0)
Requires-Dist: cffi (>=1.17.0,<2.0.0)
Requires-Dist: gkg-laser (>=1.0.0)
Requires-Dist: hslcommunication (>=1.2.0,<2.0.0)
Requires-Dist: inovance-tag-plc (>=1.0.0,<2.0.0)
Requires-Dist: mitsubishi-plc (>=1.6.0)
Requires-Dist: modbus-api (>=1.15.0,<2.0.0)
Requires-Dist: modbus-tk (>=1.1.4,<2.0.0)
Requires-Dist: mysql-api (>=1.13.0,<2.0.0)
Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
Requires-Dist: pandas (>=2.2.3,<3.0.0)
Requires-Dist: pillow (>=10.4.0,<11.0.0)
Requires-Dist: pyairtable (>=2.3.3,<3.0.0)
Requires-Dist: pymysql (>=1.1.1,<2.0.0)
Requires-Dist: python-snap7 (>=2.0.2,<3.0.0)
Requires-Dist: pythonnet (>=3.0.3,<4.0.0)
Requires-Dist: secsgempro (>=1.0.0,<2.0.0)
Requires-Dist: serialpro (>=1.0.0,<2.0.0)
Requires-Dist: siemens-plc (>=1.8.0,<2.0.0)
Requires-Dist: socket-pro (>=1.0.0,<2.0.0)
Requires-Dist: sqlalchemy (>=2.0.36,<3.0.0)
Requires-Dist: suds-community (>=1.2.0,<2.0.0)
Requires-Dist: websockets (>=14.1,<15.0)
Description-Content-Type: text/markdown

# Passive Server

基于 SECS/GEM (HSMS) 协议的半导体设备通信中间件，实现工厂 EAP/MES 与设备 PLC 之间的数据交互。支持 Equipment（设备端）和 Host（主机端）两种运行模式。

## 架构概览

### Equipment 模式（设备端）

设备端模式直接连接 PLC，监控设备状态并通过 SECS/HSMS 协议与工厂 EAP 通信。

```mermaid
graph TD 
EAP["工厂 EAP (Active)"] 
PS["PassiveServer\n(SECS/HSMS Passive)"] 
PLC["设备 PLC"] 
WEB["Web 前端页面"] 
DB["MySQL 数据库"] 
SOCKET["Socket\n下位机客户端"]
EAP -->|"SECS/HSMS\nS1~S10 流函数"| PS
PS -->|"S5F1 报警\nS6F11 事件"| EAP
PS -->|"Snap7 / Modbus\nMELSEC / Tag"| PLC
PS -->|"HTTP POST\n实时推送"| WEB
PS -->|"SQLAlchemy\n读写配置"| DB
PS -->|"异步 Socket\n数据交互"| SOCKET
```

### Host 模式（主机端）

Host 模式作为中间代理，连接工厂 EAP 和多台设备，负责指令转发和状态汇总。
```mermaid
graph TD 
EAP["工厂 EAP (Active)"] 
HP["HostPassive\n(SECS/HSMS Passive)"] 
AH["ActiveHost\n(HSMS Active)"] 
EQ1["设备 #1\n(Passive Service)"] 
EQ2["设备 #2\n(Passive Service)"] 
EQ3["设备 #N\n(Passive Service)"] 
DB["MySQL 数据库"] 
WEB["Web 前端页面"]
EAP -->|"SECS/HSMS 流函数"| HP
HP -->|"事件/报警转发"| EAP
HP -->|"指令转发\n状态汇总"| AH
AH -->|"HSMS Active"| EQ1
AH -->|"HSMS Active"| EQ2
AH -->|"HSMS Active"| EQ3
EQ1 -->|"事件/报警上报"| AH
EQ2 -->|"事件/报警上报"| AH
EQ3 -->|"事件/报警上报"| AH
HP -->|"HTTP POST"| WEB
HP -->|"SQLAlchemy"| DB
```
### 信号监控流程

设备端模式下，系统启动多个后台线程持续监控 PLC 信号变化并触发对应操作。

```mermaid
graph TD 
START["服务启动"] --> HEART["MES 心跳线程\n定时翻转心跳位"] 
START --> CTRL["控制状态监控\n本地/远程模式切换"] 
START --> PROC["运行状态监控\n运行/报警状态变化"] 
START --> RECIPE["配方监控\n当前配方变更检测"] 
START --> SIGNAL["自定义信号监控\nsignal_address_list"]
SIGNAL --> S1["Step 1: 读 PLC 地址"]
S1 --> S2["Step 2: 写 PLC 地址"]
S2 --> S3["Step 3: 执行自定义函数"]
S3 --> S4["Step 4: 等待工厂回复"]
S4 --> S5["Step 5: 发送事件 S6F11"]
S5 --> S6["Step 6: 清除信号位"]

CTRL --> EVENT1["发送 ControlStateChange 事件"]
PROC --> EVENT2["发送 ProcessStateChange 事件"]
PROC --> ALARM["触发报警 S5F1"]
```
## 功能特性

- **完整的 SECS/GEM 协议支持**：处理 S1F15/S1F17（在线/离线）、S2F17/S2F31（时间同步）、S2F33/S2F35/S2F37（报告/事件定义）、S5F1/S5F3/S5F5（报警管理）、S6F11（事件上报）、S7F1~S7F26（配方管理）、S10F3（终端显示）等标准流函数
- **多 PLC 协议支持**：西门子 S7 (Snap7)、三菱 (MELSEC)、Modbus TCP、汇川标签通讯 (Inovance Tag)
- **数据库驱动配置**：SV/EC/DV、事件、报警、报告、远程命令、PLC 地址映射等全部通过 MySQL 管理，无需修改代码
- **实时 PLC 信号监控**：监控 PLC 地址信号变化，触发自定义多步骤流程（读/写/函数调用/事件上报/等待工厂回复）
- **Socket 通信**：内置异步 Socket 服务端，支持与下位机或其他系统通信
- **Web 前端联动**：数据变更自动通知前端页面刷新，支持实时状态展示
- **日志管理**：按天自动轮转日志，统一管理 SECS 通信日志和业务日志
- **远程配方管理**：支持工厂通过 S7Fx 系列流函数下发/删除/查询配方

## 环境要求

| 依赖 | 版本                 |
|------|--------------------|
| Python | \>= 3.11, < 3.13   |
| MySQL | 5.7+ / 8.0+ / 9.0+ |
| 操作系统 | Windows            |

## 快速开始

### 安装依赖

本项目使用 [Poetry](https://python-poetry.org/) 管理依赖：

```shell
git clone https://gitee.com/w_liu/passive_server.git
cd passive_server
poetry install
```

### 编写启动类

**Equipment 模式：**
```python
import asyncio
import json
from typing import Optional

from passive_server.passive_equipment import EquipmentPassive


class EquipmentExample(EquipmentPassive):
    def __init__(self, mysql_host: str, database_name: str):
        super().__init__(mysql_host, database_name)

    def _on_rcmd_new_lot(self, lot_name: str, lot_quantity: int):
        """开工单.

        Args:
            lot_name: 工单号.
            lot_quantity: 工单数量.
        """
        data = {"new_lot": {"lot_name": lot_name, "lot_quantity": lot_quantity}}
        asyncio.run(self.send_data_to_socket_client(self.socket_server_low, "127.0.0.1", json.dumps(data)))

    def _on_rcmd_pp_select(self, recipe_name: str) -> bool:
        """切换配方.

        Args:
            recipe_name: 配方名称.

        Returns:
            Optional[bool]: True 切换成功, False 切换失败
        """
        self.logger.info("要切换的配方是 %s", recipe_name)
        if self.get_sv_value_with_name("pp_select_state") == 1:
            return True
        return False

    def _on_car_CarrierPurgeStart(self, carrier_action_dict: dict) -> tuple[int, list]:
        """收到 s3f17 下发的 carrier action 信息.

        Args:
            carrier_action_dict: carrier action 信息.

        Returns:
            tuple[int, list]: 返回 ca ack 和 错误信息列表, 不返回代表没有错误, ca_ack: 0: 没有错误, ca_ack: 不等于 0 有错误.
        """
        self.logger.info("执行 _on_car_CarrierPurgeStart")
        # todo 操作 plc
        return 0, []


if __name__ == '__main__':
    EquipmentExample("127.0.0.1", "big_beauty")
```

**Host 模式：**

```python
from passive_server.passive_host import HostPassive


class HostExample(HostPassive):
    def __init__(self):
        super().__init__()


if __name__ == '__main__':
    HostExample()
```

### 运行
**运行Equipment 模式：**
```shell
poetry run python equipment_example.py
```

**运行Host 模式：**
```shell
poetry run python host_example.py
```
## 核心数据库表

| 表名 | 说明 |
|------|------|
| `sv_list` | Status Variable 状态变量定义 |
| `dv_list` | Data Value 数据值定义 |
| `ec_list` | Equipment Constant 设备常量定义 |
| `event_list` | 事件定义 |
| `report_list` | 自定义报告定义 |
| `alarm_list` | 报警定义（支持中英文） |
| `alarm_enabled` | 工厂已启用的报警 |
| `alarm_record_list` | 报警历史记录 |
| `address_list` | PLC 地址定义 |
| `plc_address_list` | PLC→MES 地址映射（含流程步骤） |
| `mes_address_list` | MES→PLC 地址映射（含流程步骤） |
| `signal_address_list` | PLC 信号地址定义 |
| `flow_func` | 自定义流程函数配置 |
| `recipe_info` | 配方数据存储 |
| `define_report` | 工厂定义的报告 |
| `report_link_event` | 工厂定义的事件-报告关联 |
| `lot_list` | 工单列表 |
| `equipment_state` | 设备状态记录 |

## SECS 流函数支持

| 流函数 | 方向 | 说明 |
|--------|------|------|
| S1F15 → S1F16 | 工厂→设备 | 请求离线 |
| S1F17 → S1F18 | 工厂→设备 | 请求在线 |
| S2F17 → S2F18 | 工厂→设备 | 获取设备时间 |
| S2F31 → S2F32 | 工厂→设备 | 设置设备时间 |
| S2F33 → S2F34 | 工厂→设备 | 定义报告 |
| S2F35 → S2F36 | 工厂→设备 | 链接事件和报告 |
| S2F37 → S2F38 | 工厂→设备 | 启用/停用事件 |
| S2F41 → S2F42 | 工厂→设备 | Host 命令（Host模式转发） |
| S2F49 → S2F50 | 工厂→设备 | 增强远程命令 |
| S3F17 → S3F18 | 工厂→设备 | Carrier 操作 |
| S5F1 → S5F2 | 设备→工厂 | 报警上报 |
| S5F3 → S5F4 | 工厂→设备 | 启用/停用报警 |
| S5F5 → S5F6 | 工厂→设备 | 查询报警列表 |
| S6F11 → S6F12 | 设备→工厂 | 事件上报 |
| S7F1 → S7F2 | 工厂→设备 | 配方下发许可询问 |
| S7F3 → S7F4 | 工厂→设备 | 配方下发 |
| S7F5 → S7F6 | 工厂→设备 | 请求上传配方 |
| S7F17 → S7F18 | 工厂→设备 | 删除配方 |
| S7F19 → S7F20 | 工厂→设备 | 查询所有配方名称 |
| S7F23 → S7F24 | 工厂→设备 | 格式化配方下发 |
| S7F25 → S7F26 | 工厂→设备 | 格式化配方上传 |
| S10F3 → S10F4 | 工厂→设备 | 终端显示 |

## PLC 协议支持

| 协议 | PLC 类型 | 依赖库 | ec_name 标识 |
|------|---------|--------|-------------|
| S7 (Snap7) | 西门子 S7-300/400/1200/1500 | `python-snap7` | `snap7` |
| MELSEC | 三菱 FX/Q/L 系列 | `mitsubishi-plc` | `mitsubishi` |
| Modbus TCP | 通用 Modbus 设备 | `modbus-api` | `modbus` |
| 标签通讯 | 汇川 (Inovance) | `inovance-tag-plc` | `tag` |

## 核心依赖

- `secsgempro` — SECS/GEM 协议实现
- `sqlalchemy` + `pymysql` — 数据库 ORM
- `python-snap7` — 西门子 S7 PLC 通信
- `siemens-plc` — 西门子 PLC 封装
- `mitsubishi-plc` — 三菱 PLC 封装
- `inovance-tag-plc` — 汇川标签通讯
- `modbus-api` — Modbus 协议封装
- `socket-pro` — 异步 Socket 服务端
- `mysql-api` — MySQL 数据库操作封装

## 日志

日志文件存储在 `log/` 目录下，按天轮转：

- `all.log` — 当日日志
- `all_YYYY-MM-DD.log` — 历史日志

日志保留天数可通过 `ec_list` 表中的 `log_save_day` 配置。

## License

内部项目，保留所有权利。

## 作者

WeiLiu <183074632@qq.com>

