From 44762837ec94497cb08375df3ca1235e35d93339 Mon Sep 17 00:00:00 2001 From: wu <2468489804@qq.com> Date: Tue, 11 Oct 2022 15:03:01 +0800 Subject: [PATCH] =?UTF-8?q?docs:book=E5=A2=9E=E5=8A=A0scada=E7=9A=84?= =?UTF-8?q?=E6=96=87=E6=A1=A3&=E6=B8=85=E7=A9=BAreadme=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 390 ---------------------------------- docs/src/SUMMARY.md | 11 + docs/src/scada/api.md | 91 ++++++++ docs/src/scada/details.md | 39 ++++ docs/src/scada/environment.md | 77 +++++++ docs/src/scada/front.md | 91 ++++++++ docs/src/scada/plug_in.md | 6 + docs/src/scada/service.md | 48 +++++ docs/src/scada/sql_body.png | Bin 0 -> 9412 bytes docs/src/scada/sql_head.png | Bin 0 -> 11856 bytes 10 files changed, 363 insertions(+), 390 deletions(-) create mode 100644 docs/src/scada/api.md create mode 100644 docs/src/scada/details.md create mode 100644 docs/src/scada/environment.md create mode 100644 docs/src/scada/front.md create mode 100644 docs/src/scada/plug_in.md create mode 100644 docs/src/scada/service.md create mode 100644 docs/src/scada/sql_body.png create mode 100644 docs/src/scada/sql_head.png diff --git a/README.md b/README.md index 1863d57..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,390 +0,0 @@ -# EdgeManager(SCADA系统) - -- [EdgeManager(SCADA系统)](#edgemanagerscada系统) - - [API](#api) - - [class `EDataCapture`](#class-edatacapture) - - [0. HTTP POST: `set_node_data`](#0-http-post-set_node_data) - - [使用指北](#使用指北) - - [后端](#后端) - - [前端](#前端) - - [开发环境](#开发环境) - - [调试环境](#调试环境) - - [技术细节](#技术细节) - - [0. `EdgeManager\EDataCapture\EDataCapture -> set_data()`为什么是以`6710885`为大小chunked的?](#0-edgemanageredatacaptureedatacapture---set_data为什么是以6710885为大小chunked的) - -## API - -> API设计最大程度遵循了已有的MES规范。 - -### class `EDataCapture` - -#### 0. HTTP POST: `set_node_data` - -**请求** - -> 上位机程序中请单次传入尽量多的数据使得性能最大化。 -> -> 但仍需兼顾传输速率和超时时间。 -> -> 无需指定数值类型,服务端会自动根据已添加的节点信息检查,若不符则会报错。 - - -**上传设备状态** - -> 数据节点的数据类型在后台设定为设备状态时,该节点上传的值只能上传int或string两种类型。 -> -> 并且int或string这两种类型,只能上传下方表格中int或string指定的值,若不符则会报错。 -> -> 上传对应的设备状态parent_device_code为必传字段 - -| int类型 | string类型 | 描述 | -| ----- | ----- | ----- | -| 1 | IDLE | 设备已经初始化,再等待工作 | -| 2 | RUN | 设备在工作 | -| 3 | FINISH | 工序结束后,托盘还没取出时,设备为FINISH,取出后变为IDLE状态 | -| 4 | TROUBLE | 设备有问题,需要维修,这时msg有相应的关键字 | -| 5 | PAUSE | 设备通过手工操作变成暂停状态 | -| 6 | OFFLINE | 设备处于离线状态 | - -```json -{ - "action": "set_node_data", - "param": { - "working_subclass": , // 可选 - "data": [ - { - "code": , - // value的类型需与type对应 - "value": , - "device_code": [string], // 必需字段 - "parent_device_code": [string], // 可选字段,对应MES中的code - "batch": [string] // 可选字段 - }, - { - //code的值在后台的数据类别为设备状态类型时 - "code": , - // value的类型需与type对应 - "value": , - "device_code": [string], // 必需字段 - "parent_device_code": [string], // 必需字段,对应MES中的code - "batch": [string] // 可选字段 - }, - { - "code": , - "value": , - "device_code": [string], - "parent_device_code": [string], - "batch": [string] - }, - ... - ] - } -} -``` - -**返回** - -操作成功: - -```json -{ - "action": "result_set_node_data", - "errcode": 0, - "errmsg": "" -} -``` - -操作失败(**工序单元尚未登记**): - -```json -{ - "action": "result_set_node_data", - "errcode": 4002, - "errmsg": "未登记过的工序单元!" -} -``` - -操作失败(**数值类型错误**): - -```json -{ - "action": "result_set_node_data", - "errcode": 4002, - "errmsg": "节点编码和数值类型不匹配!" -} -``` - -操作失败(不明原因): - -```json -{ - "action": "result_set_node_data", - "errcode": 4002, - "errmsg": "ROLLBACKed: Bad data received (structure and/or values)" -} -``` - - -## 使用指北 - -本项目可独立运行,也可作为MES的插件使用。 - -下文简要介绍如何将代码合并入MES中。 - -需要保证`timescaledb`的版本在**2.2.0或以上**。 - -### 后端 - -添加pg扩展(Ubuntu): - -```bash -sudo apt install php-pgsql -``` - -添加pg扩展(CentOS 7): - -```bash -sudo yum install php-pgsql -``` - -添加pg扩展(CentOS8): - -```bash -sudo dnf install php-pgsql -``` - -目录结构: - -```pre -📦EdgeManager - ┣ 📂EDataCapture - ┃ ┣ 📜EDataCapture.php - ┃ ┗ 📜ENodeConfigure.php - ┣ 📜Init.php - ┗ 📜Utils.php -``` - -建议使用composer autoload引入。 - -将目录`EdgeManager`复制至项目后在composer.json内写入: - -```json -"autoload": { - "psr-4": { - "EdgeManager\\": "EdgeManager/" - }, - "files": [ - "EdgeManager/Utils.php", - "EdgeManager/Init.php" - ] -}, -``` - -运行`composer dump-autoload`即可正常调用EdgeManager的代码。 - -### 前端 - -添加依赖(**使用npm**): - -```bash -npm i element-ui \ - @d2-projects/d2-crud \ - vue-cheetah-grid \ - @d2-projects/vue-table-export \ - @d2-projects/vue-table-import \ - github-markdown-css \ - marked@^2.0.0 \ - jschardet -S -``` - -添加依赖(**使用yarn**): -```bash -yarn add element-ui \ - @d2-projects/d2-crud \ - vue-cheetah-grid \ - @d2-projects/vue-table-export \ - @d2-projects/vue-table-import \ - github-markdown-css \ - marked@^2.0.0 \ - jschardet -``` - -需要在`main.js`中增加(全局引入组件): - -```js -// D2-Crud -import ElementUI from 'element-ui' -import 'element-ui/lib/theme-chalk/index.css' -import D2Crud from '@d2-projects/d2-crud' -// Cheetah-Grid -import vueCheetahGrid from 'vue-cheetah-grid' -// 表格导出插件 -import pluginExport from '@d2-projects/vue-table-export' -import pluginImport from '@d2-projects/vue-table-import' - -Vue.use(ElementUI) -Vue.use(D2Crud) -Vue.use(vueCheetahGrid) -Vue.use(pluginExport) -Vue.use(pluginImport) -``` - -*(可选)* 在`package.json`里更改`element-ui`版本: - -```json -"element-ui": ">2.15.9 || 2.15.8", -``` - -`2.15.9`有一个小[bug](https://github.com/ElemeFE/element/issues/21941),会导致性能下降。 - -此外请参照目录结构中注释进行代码合并: - -```pre -📦src - ┣ 📂api - ┃ ┣ 📂modules - ┃ ┃ ┣ 📜scada.configure.api.js # 增添Axios请求 - ┃ ┃ ┗ 📜sys.user.api.js - ┃ ┣ 📜index.js - ┃ ┣ 📜service.js - ┃ ┗ 📜tools.js - ┣ 📂assets - ┣ 📂components - ┃ ┣ 📂d2-markdown # 渲染markdown所需组件(在D2Admin的基础上精简了功能) - ┃ ┃ ┗ 📜index.vue - ┃ ┗ 📜index.js # 在此处注册d2-markdown - ┣ 📂libs - ┣ 📂locales - ┣ 📂menu - ┃ ┗ 📜index.js # 增添菜单 - ┣ 📂plugin - ┣ 📂router - ┃ ┣ 📜index.js - ┃ ┗ 📜routes.js # 增添路由 - ┣ 📂store - ┣ 📂views - ┃ ┣ 📂scada # 增添页面 - ┃ ┃ ┣ 📂scadaConfigure - ┃ ┃ ┃ ┗ 📜index.vue - ┃ ┃ ┗ 📂scadaQuery - ┃ ┃ ┃ ┗ 📜index.vue - ┃ ┗ 📂system - ┣ 📜App.vue - ┣ 📜i18n.js - ┣ 📜main.js - ┗ 📜setting.js -``` - -## 开发环境 - -拉取代码: - -```bash -# 建议先配置SSH key pair -git clone ssh://git@118.195.187.246:10022/ysun/EdgeManager.git -cd EdgeManager -``` - -一键部署PHP workerman和TimescaleDB环境: - -```bash -# docker build --network host -t edge_manager . -docker compose up -d -``` - -进入交互式Prompt: - -```bash -docker exec -it edge_manager bash -``` - -后端调试: - -```bash -# In container -# --no_dup_code:禁止code在不同的working subclass间复用 -# --relay_device_status:不判断是否是设备状态并转发到MES接口 -php EdgeManager.php --no_dup_code --relay_device_status --server_name=GPU-server-01 --user=postgres --password=big_dick start -``` - -前端调试: - -```bash -# In host -# yarn -# yarn watch -yarn serve -``` - -客户端连接: - -```bash -# sudo apt install postgresql-client -# 登入 -psql -h localhost -U postgres -# 显示数据库列表 -\l -``` - -## 调试环境 - -不使用`docker compose`创建两个container分别运行EdgeManager和pg: - -```bash -# cd EdgeManager # 先定位到项目目录,方便创建image和挂载 -# 创建EdgeManager的image -docker build --network host -t edge_manager . -# 创建container运行EdgeManager -docker run -d --name edge_manager_test -v $PWD:/EdgeManager --network host --ipc host -it edge_manager -# 创建container运行pg,将端口映射到host的55432 -docker run -d --name pg_test -v $PWD/config/postgresql.conf:/etc/postgresql/postgresql.conf -p 55432:5432 -e POSTGRES_PASSWORD=big_dick -it timescale/timescaledb-ha:pg14-latest postgres -c 'config_file=/etc/postgresql/postgresql.conf' -# 进入交互式Prompt -docker exec -it edge_manager_test bash -# 启动EdgeManager(workerman)命令省略... - -# 常用命令 -# 查看全部container -docker ps -a -# 启动已停止的container -docker start [container] -``` - -## 技术细节 - -### 0. `EdgeManager\EDataCapture\EDataCapture -> set_data()`为什么是以`6710885`为大小chunked的? - -首先明确一点,根据PostgreSQL的技术架构,条件允许的情况下,**一次性插入多条记录是效率最高的**,比如: - -```sql -INSERT INTO films (code, title, did, date_prod, kind) VALUES - ('B6717', 'Tampopo', 110, '1985-02-10', 'Comedy'), - ('HG120', 'The Dinner Game', 140, DEFAULT, 'Comedy'); -``` - -这其中,仅插入部分字段的值,比如: - -```sql -INSERT INTO films (code, title, did, date_prod, kind) - VALUES ('T_601', 'Yojimbo', 106, '1961-06-16', 'Drama'); -``` - -的效率又远远不如插入全部列,但使用特殊变量DEFAULT替代缺少的字段,比如: - -```sql -INSERT INTO films VALUES - ('UA502', 'Bananas', DEFAULT, '1971-07-13', 'Comedy', '82 minutes'); -``` - -PostgreSQL的文档里没有提及这个话题,因为源码里为SQL语句的内存分配[指定了一个最大长度常量`MaxAllocSize`](https://github.com/postgres/postgres/blob/2373fe78dfc9d4aa2348a86fffdf8eb9d757e9d5/src/common/stringinfo.c#L28),其大小为比1GB小1字节。这个值可以在编译前手动修改,所以理论上每个PG运行的实例都可以不一样,如果我们使用的PG并非修改过源码手动编译的,那么SQL语句的最大长度为`1024 ** 3 - 1 = 1073741823` bytes。 - -> 安全起见,下述计算假定working_subclass等字段的最大长度均为我们以往约定的45 bytes。 - -而此处我源码中的SQL语句头长度为138 bytes(注意末尾空格): - -![](imgs/sql_head.png) - -单个语句体的长度为160 bytes: - -![](imgs/sql_body.png) - -所以其最多可以一次性插入`(1073741823 - 138) / 160`条记录,向下取整后即为`6710885`。 diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index ba304b9..78c046b 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -4,6 +4,17 @@ [系统配置](system.md) + +# SCADA系统 + +- [环境搭建](scada/environment.md) +- [作为MES插件使用](scada/plug_in.md) + - [前端配置](scada/front.md) + - [后端配置](scada/service.md) +- [API](scada/API.md) +- [技术细节](scada/details.md) + + # 测试 - [集群搭建](tests/cluster.md) diff --git a/docs/src/scada/api.md b/docs/src/scada/api.md new file mode 100644 index 0000000..b23858b --- /dev/null +++ b/docs/src/scada/api.md @@ -0,0 +1,91 @@ +# API +> API设计最大程度遵循了已有的MES规范。 + +## 请求说明 +>上位机程序中请单次传入尽量多的数据使得性能最大化。 +> +>但仍需兼顾传输速率和超时时间。 +> +>无需指定数值类型,服务端会自动根据已添加的节点信息检查,若不符则会报错。 + +### set_node_data,上传设备实时数据 +#### 输入接口参数说明: + +| 参数名称 | 参数说明 | 类型 | 是否必传 |备注 | +| ---- | ---- | ---- | ---- |---- | +| action | 接口名称 |String | 是 | +| param | 接口参数 |Object | 是| +| working_subclass | 工序单元属性 |String | 可选 | 通过数据系统对各工序动态定义| +| data | 上传数值 |Array | 是| +| code | 值 | String | 是 | +| value | 值 |string/int/float/bool | 是 | value的类型需与数据库节点配置表的type对应| +| device_code | 设备编码 |string | 是 | +| parent_device_code | 值 | String | 可选 | MES系统中的设备编号,上传设备状态时为必传| +| batch | 批次号 | String | 可选 | MES系统中的生产批次号| + +```json +{ + "action": "set_node_data", + "param": { + "working_subclass": , // 可选 + "data": [ + { + "code": , + "value": ,// value的类型需与type对应 + "device_code": [string], // 必需字段 + "parent_device_code": [string], // 可选字段,对应MES中的device_code + "batch": [string] // 可选字段 + }, + { + //code的值在后台的数据类别为设备状态类型时 + "code": , + // value的类型需与type对应 + "value": , + "device_code": [string], // 必需字段 + "parent_device_code": [string], // 必需字段,对应MES中的code + "batch": [string] // 可选字段 + }, + { + "code": , + "value": , + "device_code": [string], + "parent_device_code": [string], + "batch": [string] + }, + ... + ] + } +} +``` + +#### 1.2 输出接口参数说明: + +| 参数名称 | 参数说明 | 类型 | 长度 |备注 | +| ----- | ----- | ----- | ----- |----- | +| action | 接口名称 |String | 45 | +| errcode | 返回码 |Int | 11 |0:请求成功
4002:未登记过的工序单元!
4002:节点编码和数值类型不匹配!
4002:ROLLBACKed: Bad data received (structure and/or values) | +| errmsg | 错误信息 |String | 255 |errcode 不为0,errmsg才有错误信息 | + +``` +{ + action: "result_set_node_data", + errcode: 0, + errmsg: "" +} +``` + +## 上传设备状态 +>数据节点的数据类型在后台设定为设备状态时,该节点上传的值只能上传int或string两种类型。 +> +>并且int或string这两种类型,只能上传下方表格中int或string指定的值,若不符则会报错。 +> +>上传对应的设备状态parent_device_code为必传字段 + +| int类型 | string类型 | 描述 | +| ----- | ----- | ----- | +| 1 | IDLE | 设备已经初始化,再等待工作 | +| 2 | RUN | 设备在工作 | +| 3 | FINISH | 工序结束后,托盘还没取出时,设备为FINISH,取出后变为IDLE状态 | +| 4 | TROUBLE | 设备有问题,需要维修,这时msg有相应的关键字 | +| 5 | PAUSE | 设备通过手工操作变成暂停状态 | +| 6 | OFFLINE | 设备处于离线状态 | \ No newline at end of file diff --git a/docs/src/scada/details.md b/docs/src/scada/details.md new file mode 100644 index 0000000..a18bc57 --- /dev/null +++ b/docs/src/scada/details.md @@ -0,0 +1,39 @@ +## 技术细节 + +### 0. `EdgeManager\EDataCapture\EDataCapture -> set_data()`为什么是以`6710885`为大小chunked的? + +首先明确一点,根据PostgreSQL的技术架构,条件允许的情况下,**一次性插入多条记录是效率最高的**,比如: + +```sql +INSERT INTO films (code, title, did, date_prod, kind) VALUES + ('B6717', 'Tampopo', 110, '1985-02-10', 'Comedy'), + ('HG120', 'The Dinner Game', 140, DEFAULT, 'Comedy'); +``` + +这其中,仅插入部分字段的值,比如: + +```sql +INSERT INTO films (code, title, did, date_prod, kind) + VALUES ('T_601', 'Yojimbo', 106, '1961-06-16', 'Drama'); +``` + +的效率又远远不如插入全部列,但使用特殊变量DEFAULT替代缺少的字段,比如: + +```sql +INSERT INTO films VALUES + ('UA502', 'Bananas', DEFAULT, '1971-07-13', 'Comedy', '82 minutes'); +``` + +PostgreSQL的文档里没有提及这个话题,因为源码里为SQL语句的内存分配[指定了一个最大长度常量`MaxAllocSize`](https://github.com/postgres/postgres/blob/2373fe78dfc9d4aa2348a86fffdf8eb9d757e9d5/src/common/stringinfo.c#L28),其大小为比1GB小1字节。这个值可以在编译前手动修改,所以理论上每个PG运行的实例都可以不一样,如果我们使用的PG并非修改过源码手动编译的,那么SQL语句的最大长度为`1024 ** 3 - 1 = 1073741823` bytes。 + +> 安全起见,下述计算假定working_subclass等字段的最大长度均为我们以往约定的45 bytes。 + +而此处我源码中的SQL语句头长度为138 bytes(注意末尾空格): + +![](sql_head.png) + +单个语句体的长度为160 bytes: + +![](sql_body.png) + +所以其最多可以一次性插入`(1073741823 - 138) / 160`条记录,向下取整后即为`6710885`。 \ No newline at end of file diff --git a/docs/src/scada/environment.md b/docs/src/scada/environment.md new file mode 100644 index 0000000..0b23772 --- /dev/null +++ b/docs/src/scada/environment.md @@ -0,0 +1,77 @@ +# 环境搭建 +## 开发环境 + +拉取代码: + +```bash +# 建议先配置SSH key pair +git clone ssh://git@118.195.187.246:10022/ysun/EdgeManager.git +cd EdgeManager +``` + +一键部署PHP workerman和TimescaleDB环境: + +```bash +# docker build --network host -t edge_manager . +docker compose up -d +``` + +进入交互式Prompt: + +```bash +docker exec -it edge_manager sh +``` + +后端调试: + +>--no_dup_code:禁止code在不同的working subclass间复用 +> +>--relay_device_status:不判断是否是设备状态并转发到MES接口 +> +>--port:连接PG数据库端口,默认5432端口 + +```bash +# In container +php EdgeManager.php --no_dup_code --relay_device_status --server_name=GPU-server-01 --user=postgres --password=big_dick start +``` + +前端调试: + +```bash +# In host +# yarn +# yarn watch +yarn serve +``` + +客户端PG连接: + +```bash +# sudo apt install postgresql-client +# 登入 +psql -h localhost -U postgres +# 显示数据库列表 +\l +``` + +## 调试环境 + +不使用`docker compose`创建两个container分别运行EdgeManager和pg: + +```bash +# cd EdgeManager # 先定位到项目目录,方便创建image和挂载 +# 创建EdgeManager的image +docker build --network host -t edge_manager . +# 创建container运行EdgeManager +docker run -d --name edge_manager_test -v $PWD:/EdgeManager --network host --ipc host -it edge_manager +# 创建container运行pg,将端口映射到host的55432 +docker run -d --name pg_test -v $PWD/config/postgresql.conf:/etc/postgresql/postgresql.conf -p 55432:5432 -e POSTGRES_PASSWORD=big_dick -it timescale/timescaledb-ha:pg14-latest postgres -c 'config_file=/etc/postgresql/postgresql.conf' +# 进入交互式Prompt +docker exec -it edge_manager_test sh +# 启动EdgeManager(workerman)命令省略... + +# 常用命令 +# 查看全部container +docker ps -a +# 启动已停止的container +docker start [container] diff --git a/docs/src/scada/front.md b/docs/src/scada/front.md new file mode 100644 index 0000000..47a6639 --- /dev/null +++ b/docs/src/scada/front.md @@ -0,0 +1,91 @@ +# 前端配置 +添加依赖(**使用npm**): + +```bash +npm i element-ui \ + @d2-projects/d2-crud \ + vue-cheetah-grid \ + @d2-projects/vue-table-export \ + @d2-projects/vue-table-import \ + github-markdown-css \ + marked@^2.0.0 \ + jschardet -S +``` + +添加依赖(**使用yarn**): +```bash +yarn add element-ui \ + @d2-projects/d2-crud \ + vue-cheetah-grid \ + @d2-projects/vue-table-export \ + @d2-projects/vue-table-import \ + github-markdown-css \ + marked@^2.0.0 \ + jschardet +``` + +需要在`main.js`中增加(全局引入组件): + +```js +// D2-Crud +import ElementUI from 'element-ui' +import 'element-ui/lib/theme-chalk/index.css' +import D2Crud from '@d2-projects/d2-crud' +// Cheetah-Grid +import vueCheetahGrid from 'vue-cheetah-grid' +// 表格导出插件 +import pluginExport from '@d2-projects/vue-table-export' +import pluginImport from '@d2-projects/vue-table-import' + +Vue.use(ElementUI) +Vue.use(D2Crud) +Vue.use(vueCheetahGrid) +Vue.use(pluginExport) +Vue.use(pluginImport) +``` + +*(可选)* 在`package.json`里更改`element-ui`版本: + +```json +"element-ui": ">2.15.9 || 2.15.8", +``` + +`2.15.9`有一个小[bug](https://github.com/ElemeFE/element/issues/21941),会导致性能下降。 + +此外请参照目录结构中注释进行代码合并: + +```pre +📦src + ┣ 📂api + ┃ ┣ 📂modules + ┃ ┃ ┣ 📜scada.configure.api.js # 增添Axios请求 + ┃ ┃ ┗ 📜sys.user.api.js + ┃ ┣ 📜index.js + ┃ ┣ 📜service.js + ┃ ┗ 📜tools.js + ┣ 📂assets + ┣ 📂components + ┃ ┣ 📂d2-markdown # 渲染markdown所需组件(在D2Admin的基础上精简了功能) + ┃ ┃ ┗ 📜index.vue + ┃ ┗ 📜index.js # 在此处注册d2-markdown + ┣ 📂libs + ┣ 📂locales + ┣ 📂menu + ┃ ┗ 📜index.js # 增添菜单 + ┣ 📂plugin + ┣ 📂router + ┃ ┣ 📜index.js + ┃ ┗ 📜routes.js # 增添路由 + ┣ 📂store + ┣ 📂views + ┃ ┣ 📂scada # 增添页面 + ┃ ┃ ┣ 📂scadaConfigure + ┃ ┃ ┃ ┗ 📜index.vue + ┃ ┃ ┗ 📂scadaQuery + ┃ ┃ ┃ ┗ 📜index.vue + ┃ ┗ 📂system + ┣ 📜App.vue + ┣ 📜i18n.js + ┣ 📜main.js + ┗ 📜setting.js +``` \ No newline at end of file diff --git a/docs/src/scada/plug_in.md b/docs/src/scada/plug_in.md new file mode 100644 index 0000000..7dfda28 --- /dev/null +++ b/docs/src/scada/plug_in.md @@ -0,0 +1,6 @@ +# SCADA系统 +> 本项目可独立运行,也可作为MES的插件使用。 +> +> 下面章节简要介绍如何将代码合并入MES中。 +> +> 需要保证timescaledb的版本在2.2.0或以上。 \ No newline at end of file diff --git a/docs/src/scada/service.md b/docs/src/scada/service.md new file mode 100644 index 0000000..a4c8a23 --- /dev/null +++ b/docs/src/scada/service.md @@ -0,0 +1,48 @@ +# 后端配置 + +添加pg扩展(Ubuntu): + +```bash +sudo apt install php-pgsql +``` + +添加pg扩展(CentOS 7): + +```bash +sudo yum install php-pgsql +``` + +添加pg扩展(CentOS8): + +```bash +sudo dnf install php-pgsql +``` + +目录结构: + +```pre +📦EdgeManager + ┣ 📂EDataCapture + ┃ ┣ 📜EDataCapture.php + ┃ ┗ 📜ENodeConfigure.php + ┣ 📜Init.php + ┗ 📜Utils.php +``` + +建议使用composer autoload引入。 + +将目录`EdgeManager`复制至项目后在composer.json内写入: + +```json +"autoload": { + "psr-4": { + "EdgeManager\\": "EdgeManager/" + }, + "files": [ + "EdgeManager/Utils.php", + "EdgeManager/Init.php" + ] +}, +``` + +运行`composer dump-autoload`即可正常调用EdgeManager的代码。 \ No newline at end of file diff --git a/docs/src/scada/sql_body.png b/docs/src/scada/sql_body.png new file mode 100644 index 0000000000000000000000000000000000000000..c96d713b302e693811a9a0af5b375d41a1ad9e5b GIT binary patch literal 9412 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 K7G1Xg>)!x(lZ&nZ literal 0 HcmV?d00001 diff --git a/docs/src/scada/sql_head.png b/docs/src/scada/sql_head.png new file mode 100644 index 0000000000000000000000000000000000000000..314cf662ffd030d8d86502798358b5534951c84f GIT binary patch literal 11856 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 Xrs8w$^VdQ`Q^e+iy;Zs8jYt0lYD?Ua literal 0 HcmV?d00001