new-g
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# QR/Barcode Scanner
|
||||
|
||||
**pyzbar** + **OpenCV** для **Raspberry Pi Camera Module v3**.
|
||||
|
||||
## Зависимости
|
||||
|
||||
```bash
|
||||
sudo apt install libzbar0 libgstreamer1.0-dev
|
||||
pip install opencv-python pyzbar
|
||||
```
|
||||
|
||||
## Запуск
|
||||
|
||||
```bash
|
||||
python scanner.py
|
||||
```
|
||||
|
||||
## Поддерживаемые форматы
|
||||
|
||||
- QR-код
|
||||
- Code 128, Code 39, EAN-13, EAN-8
|
||||
- UPC-A, UPC-E
|
||||
- Interleaved 2 of 5
|
||||
|
||||
## Управление
|
||||
|
||||
- `q` - Выход
|
||||
- `r` - Сброс списка отсканированных кодов
|
||||
|
||||
## Как работает
|
||||
|
||||
1. Подключение камеры через GStreamer (`libcamerasrc`) — дефолт путь для Camera Module v3
|
||||
2. Каждый кадр конвертируется в градации серого и передаётся в `pyzbar.decode()`
|
||||
3. Найденные коды обводятся зелёной рамкой, данные выводятся в консоль и на экран
|
||||
4. Дубликаты фильтруются — каждый уникальный код выводится один раз (до сброса)
|
||||
|
||||
## Fallback
|
||||
|
||||
Если GStreamer-пайплайн недоступен, скрипт автоматически пробует открыть камеру через `/dev/video0` (V4L2).
|
||||
@@ -0,0 +1,139 @@
|
||||
# Rostpoliplast — Линия переработки лома во вторичный гранулят
|
||||
|
||||
Система автоматизации линии переработки полимерного лома с контролем этапов, телеметрией и QR-сканированием.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Датчики │──▶│ Edge GW │──▶│ MQTT │──▶│ Backend │
|
||||
│ (IO-Link) │ │ (ESP32) │ │ Broker │ │ (Go) │
|
||||
└─────────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||
│
|
||||
┌─────┴─────┐
|
||||
│ InfluxDB │
|
||||
│ Grafana │
|
||||
└───────────┘
|
||||
```
|
||||
|
||||
## Backend API
|
||||
|
||||
### Запуск
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Swagger: `http://localhost:8080/swagger/index.html`
|
||||
|
||||
### Эндпоинты
|
||||
|
||||
#### Тюки (Bales)
|
||||
| Метод | Путь | Описание |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/v1/bales` | Список всех тюков |
|
||||
| `GET` | `/api/v1/bales/:id` | Тюк по ID |
|
||||
| `POST` | `/api/v1/bales` | Создать тюк (+ RabbitMQ task) |
|
||||
| `PUT` | `/api/v1/bales/:id` | Обновить тюк |
|
||||
| `DELETE` | `/api/v1/bales/:id` | Удалить тюк |
|
||||
| `POST` | `/api/v1/bales?type=standard` | Создать по типу (QR flow) |
|
||||
|
||||
#### Типы тюков (Bale Types)
|
||||
| Метод | Путь | Описание |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/v1/bale-types` | Список типов |
|
||||
| `GET` | `/api/v1/bale-types/:id` | Тип по ID |
|
||||
| `GET` | `/api/v1/bale-types/qr/:id` | QR-код для маркировки |
|
||||
| `POST` | `/api/v1/bale-types` | Создать тип |
|
||||
| `PUT` | `/api/v1/bale-types/:id` | Обновить тип |
|
||||
| `DELETE` | `/api/v1/bale-types/:id` | Удалить тип |
|
||||
|
||||
#### Линия (Line) — **НОВОЕ**
|
||||
| Метод | Путь | Описание |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/v1/line/stages` | Все этапы линии |
|
||||
| `GET` | `/api/v1/line/stages/:id` | Этап по ID |
|
||||
| `POST` | `/api/v1/line/stages` | Добавить этап |
|
||||
| `PUT` | `/api/v1/line/stages/:id` | Обновить этап |
|
||||
| `DELETE` | `/api/v1/line/stages/:id` | Удалить этап |
|
||||
| `GET` | `/api/v1/line/stats` | Статистика линии |
|
||||
|
||||
#### Датчики (Sensors) — **НОВОЕ**
|
||||
| Метод | Путь | Описание |
|
||||
|---|---|---|
|
||||
| `POST` | `/api/v1/sensors/readings` | Показание датчика |
|
||||
| `GET` | `/api/v1/sensors/readings/stage/:id` | История по этапу |
|
||||
|
||||
#### События (Events) — **НОВОЕ**
|
||||
| Метод | Путь | Описание |
|
||||
|---|---|---|
|
||||
| `POST` | `/api/v1/events` | Событие на линии |
|
||||
| `GET` | `/api/v1/events/stage/:id` | События по этапу |
|
||||
|
||||
#### Очередь (Queue)
|
||||
| Метод | Путь | Описание |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/v1/queue/next` | Следующая задача |
|
||||
|
||||
### Примеры запросов
|
||||
|
||||
```bash
|
||||
# Получить этапы линии
|
||||
curl http://localhost:8080/api/v1/line/stages
|
||||
|
||||
# Отправить показание датчика
|
||||
curl -X POST http://localhost:8080/api/v1/sensors/readings \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"stage_id": 1, "sensor": "vibration", "value": 42.5, "unit": "mm/s"}'
|
||||
|
||||
# Создать событие
|
||||
curl -X POST http://localhost:8080/api/v1/events \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"stage_id": 3, "event_type": "item_processed", "data": "success"}'
|
||||
|
||||
# Статистика линии
|
||||
curl http://localhost:8080/api/v1/line/stats
|
||||
```
|
||||
|
||||
## Компоненты линии (`Line/`)
|
||||
|
||||
| Компонент | Описание |
|
||||
|---|---|
|
||||
| `controller_*` | Контроллеры (WAGO, Siemens, Beckhoff, RPi) |
|
||||
| `drive_*` | ЧРП (Danfoss, ABB, Schneider, SEW, Lenze) |
|
||||
| `sensor_*` | Датчики (вибрация, температура, позиция, ток, уровень) |
|
||||
| `safety_*` | Безопасность (Pilz, Siemens, Schneider) |
|
||||
| `middleware_*` | Node-RED, Mosquitto, Grafana+InfluxDB |
|
||||
| `comm_*` | Коммуникация (Hilscher, Moxa, Turck) |
|
||||
| `infrastructure_*` | Шкафы, питание, коммутаторы |
|
||||
|
||||
## QR Сканер
|
||||
|
||||
```bash
|
||||
python QR/scanner.py
|
||||
```
|
||||
|
||||
- Камера: Raspberry Pi Camera Module v3
|
||||
- Библиотеки: `pyzbar` + `OpenCV`
|
||||
- Клавиши: `q` — выход, `r` — сброс
|
||||
|
||||
## Счетчик товаров (mmWave)
|
||||
|
||||
```yaml
|
||||
# ESPHome + LD2410B
|
||||
esphome run ld2410b_counter.yaml
|
||||
```
|
||||
|
||||
- Радар 24 ГГц, отслеживание дистанции и энергии
|
||||
- Подсчет через конечный автомат (debounce 300ms)
|
||||
- MQTT интеграция, настраиваемые пороги
|
||||
|
||||
## Тесты
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
go test ./... -v
|
||||
```
|
||||
|
||||
20 тестов покрывают все эндпоинты API.
|
||||
|
||||
+487
-1
@@ -73,6 +73,38 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bale-types/qr/{id}": {
|
||||
"get": {
|
||||
"description": "Возвращает QR код с URL для регистрации тюка этого типа",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"image/png"
|
||||
],
|
||||
"tags": [
|
||||
"bale-types"
|
||||
],
|
||||
"summary": "Получить QR код для маркировки тюка",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ID типа тюка",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bale-types/{id}": {
|
||||
"get": {
|
||||
"description": "Возвращает тип тюка по ID",
|
||||
@@ -219,10 +251,15 @@ const docTemplate = `{
|
||||
"description": "Данные тюка",
|
||||
"name": "bale",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.Bale"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Тип тюка (для QR кодов)",
|
||||
"name": "type",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -338,6 +375,362 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/events": {
|
||||
"post": {
|
||||
"description": "Создаёт новое событие на этапе производственной линии",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"events"
|
||||
],
|
||||
"summary": "Создать событие на линии",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Данные события",
|
||||
"name": "event",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.ProductionEvent"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.ProductionEvent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/events/stage/{id}": {
|
||||
"get": {
|
||||
"description": "Возвращает последние 100 событий для указанного этапа",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"events"
|
||||
],
|
||||
"summary": "Получить события по этапу",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ID этапа",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.ProductionEvent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/line/stages": {
|
||||
"get": {
|
||||
"description": "Возвращает список всех этапов производственной линии",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"line"
|
||||
],
|
||||
"summary": "Получить все этапы линии",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Создаёт новый этап производственной линии",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"line"
|
||||
],
|
||||
"summary": "Создать этап линии",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Данные этапа",
|
||||
"name": "stage",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/line/stages/{id}": {
|
||||
"get": {
|
||||
"description": "Возвращает этап линии по ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"line"
|
||||
],
|
||||
"summary": "Получить этап линии по ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ID этапа",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Обновляет данные этапа линии по ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"line"
|
||||
],
|
||||
"summary": "Обновить этап линии",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ID этапа",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Данные этапа",
|
||||
"name": "stage",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Удаляет этап линии по ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"line"
|
||||
],
|
||||
"summary": "Удалить этап линии",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ID этапа",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/line/stats": {
|
||||
"get": {
|
||||
"description": "Возвращает сводную статистику работы производственной линии",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"line"
|
||||
],
|
||||
"summary": "Получить статистику линии",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStats"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/queue/next": {
|
||||
"get": {
|
||||
"description": "Получает и удаляет из очереди следующую задачу (FIFO)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"queue"
|
||||
],
|
||||
"summary": "Получить следующую задачу из очереди",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sensors/readings": {
|
||||
"post": {
|
||||
"description": "Создаёт новое показание датчика для этапа линии",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"sensors"
|
||||
],
|
||||
"summary": "Создать показание датчика",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Данные показания",
|
||||
"name": "reading",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.SensorReading"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.SensorReading"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sensors/readings/stage/{id}": {
|
||||
"get": {
|
||||
"description": "Возвращает последние 100 показаний датчиков для указанного этапа",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"sensors"
|
||||
],
|
||||
"summary": "Получить показания датчиков по этапу",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ID этапа",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.SensorReading"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -382,6 +775,99 @@ const docTemplate = `{
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage": {
|
||||
"description": "Этап производственной линии переработки",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"equipment": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"mqtt_topic": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"order": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStats": {
|
||||
"description": "Сводная статистика работы производственной линии",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avg_speed": {
|
||||
"type": "number"
|
||||
},
|
||||
"current_stage": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_update_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"rejected_items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"total_items": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.ProductionEvent": {
|
||||
"description": "Событие, произошедшее на этапе производственной линии",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string"
|
||||
},
|
||||
"event_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"stage_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.SensorReading": {
|
||||
"description": "Показание датчика на этапе линии",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"sensor": {
|
||||
"type": "string"
|
||||
},
|
||||
"stage_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
},
|
||||
"unit": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
|
||||
+487
-1
@@ -62,6 +62,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bale-types/qr/{id}": {
|
||||
"get": {
|
||||
"description": "Возвращает QR код с URL для регистрации тюка этого типа",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"image/png"
|
||||
],
|
||||
"tags": [
|
||||
"bale-types"
|
||||
],
|
||||
"summary": "Получить QR код для маркировки тюка",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ID типа тюка",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bale-types/{id}": {
|
||||
"get": {
|
||||
"description": "Возвращает тип тюка по ID",
|
||||
@@ -208,10 +240,15 @@
|
||||
"description": "Данные тюка",
|
||||
"name": "bale",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.Bale"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Тип тюка (для QR кодов)",
|
||||
"name": "type",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -327,6 +364,362 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/events": {
|
||||
"post": {
|
||||
"description": "Создаёт новое событие на этапе производственной линии",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"events"
|
||||
],
|
||||
"summary": "Создать событие на линии",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Данные события",
|
||||
"name": "event",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.ProductionEvent"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.ProductionEvent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/events/stage/{id}": {
|
||||
"get": {
|
||||
"description": "Возвращает последние 100 событий для указанного этапа",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"events"
|
||||
],
|
||||
"summary": "Получить события по этапу",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ID этапа",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.ProductionEvent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/line/stages": {
|
||||
"get": {
|
||||
"description": "Возвращает список всех этапов производственной линии",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"line"
|
||||
],
|
||||
"summary": "Получить все этапы линии",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Создаёт новый этап производственной линии",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"line"
|
||||
],
|
||||
"summary": "Создать этап линии",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Данные этапа",
|
||||
"name": "stage",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/line/stages/{id}": {
|
||||
"get": {
|
||||
"description": "Возвращает этап линии по ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"line"
|
||||
],
|
||||
"summary": "Получить этап линии по ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ID этапа",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Обновляет данные этапа линии по ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"line"
|
||||
],
|
||||
"summary": "Обновить этап линии",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ID этапа",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Данные этапа",
|
||||
"name": "stage",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Удаляет этап линии по ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"line"
|
||||
],
|
||||
"summary": "Удалить этап линии",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ID этапа",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/line/stats": {
|
||||
"get": {
|
||||
"description": "Возвращает сводную статистику работы производственной линии",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"line"
|
||||
],
|
||||
"summary": "Получить статистику линии",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStats"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/queue/next": {
|
||||
"get": {
|
||||
"description": "Получает и удаляет из очереди следующую задачу (FIFO)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"queue"
|
||||
],
|
||||
"summary": "Получить следующую задачу из очереди",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sensors/readings": {
|
||||
"post": {
|
||||
"description": "Создаёт новое показание датчика для этапа линии",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"sensors"
|
||||
],
|
||||
"summary": "Создать показание датчика",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Данные показания",
|
||||
"name": "reading",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.SensorReading"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.SensorReading"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sensors/readings/stage/{id}": {
|
||||
"get": {
|
||||
"description": "Возвращает последние 100 показаний датчиков для указанного этапа",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"sensors"
|
||||
],
|
||||
"summary": "Получить показания датчиков по этапу",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ID этапа",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.SensorReading"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -371,6 +764,99 @@
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage": {
|
||||
"description": "Этап производственной линии переработки",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"equipment": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"mqtt_topic": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"order": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStats": {
|
||||
"description": "Сводная статистика работы производственной линии",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avg_speed": {
|
||||
"type": "number"
|
||||
},
|
||||
"current_stage": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_update_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"rejected_items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"total_items": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.ProductionEvent": {
|
||||
"description": "Событие, произошедшее на этапе производственной линии",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string"
|
||||
},
|
||||
"event_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"stage_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.SensorReading": {
|
||||
"description": "Показание датчика на этапе линии",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"sensor": {
|
||||
"type": "string"
|
||||
},
|
||||
"stage_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
},
|
||||
"unit": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
|
||||
+321
-1
@@ -27,6 +27,68 @@ definitions:
|
||||
width:
|
||||
type: number
|
||||
type: object
|
||||
gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage:
|
||||
description: Этап производственной линии переработки
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
equipment:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
mqtt_topic:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
order:
|
||||
type: integer
|
||||
type: object
|
||||
gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStats:
|
||||
description: Сводная статистика работы производственной линии
|
||||
properties:
|
||||
avg_speed:
|
||||
type: number
|
||||
current_stage:
|
||||
type: string
|
||||
last_update_time:
|
||||
type: string
|
||||
rejected_items:
|
||||
type: integer
|
||||
status:
|
||||
type: string
|
||||
total_items:
|
||||
type: integer
|
||||
type: object
|
||||
gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.ProductionEvent:
|
||||
description: Событие, произошедшее на этапе производственной линии
|
||||
properties:
|
||||
data:
|
||||
type: string
|
||||
event_type:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
stage_id:
|
||||
type: integer
|
||||
timestamp:
|
||||
type: string
|
||||
type: object
|
||||
gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.SensorReading:
|
||||
description: Показание датчика на этапе линии
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
sensor:
|
||||
type: string
|
||||
stage_id:
|
||||
type: integer
|
||||
timestamp:
|
||||
type: string
|
||||
unit:
|
||||
type: string
|
||||
value:
|
||||
type: number
|
||||
type: object
|
||||
info:
|
||||
contact: {}
|
||||
paths:
|
||||
@@ -137,6 +199,27 @@ paths:
|
||||
summary: Обновить тип тюка
|
||||
tags:
|
||||
- bale-types
|
||||
/bale-types/qr/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Возвращает QR код с URL для регистрации тюка этого типа
|
||||
parameters:
|
||||
- description: ID типа тюка
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- image/png
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: file
|
||||
summary: Получить QR код для маркировки тюка
|
||||
tags:
|
||||
- bale-types
|
||||
/bales:
|
||||
get:
|
||||
consumes:
|
||||
@@ -162,9 +245,12 @@ paths:
|
||||
- description: Данные тюка
|
||||
in: body
|
||||
name: bale
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.Bale'
|
||||
- description: Тип тюка (для QR кодов)
|
||||
in: query
|
||||
name: type
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -244,6 +330,240 @@ paths:
|
||||
summary: Обновить тюк
|
||||
tags:
|
||||
- bales
|
||||
/events:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Создаёт новое событие на этапе производственной линии
|
||||
parameters:
|
||||
- description: Данные события
|
||||
in: body
|
||||
name: event
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.ProductionEvent'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.ProductionEvent'
|
||||
summary: Создать событие на линии
|
||||
tags:
|
||||
- events
|
||||
/events/stage/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Возвращает последние 100 событий для указанного этапа
|
||||
parameters:
|
||||
- description: ID этапа
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.ProductionEvent'
|
||||
type: array
|
||||
summary: Получить события по этапу
|
||||
tags:
|
||||
- events
|
||||
/line/stages:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Возвращает список всех этапов производственной линии
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage'
|
||||
type: array
|
||||
summary: Получить все этапы линии
|
||||
tags:
|
||||
- line
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Создаёт новый этап производственной линии
|
||||
parameters:
|
||||
- description: Данные этапа
|
||||
in: body
|
||||
name: stage
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage'
|
||||
summary: Создать этап линии
|
||||
tags:
|
||||
- line
|
||||
/line/stages/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Удаляет этап линии по ID
|
||||
parameters:
|
||||
- description: ID этапа
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
type: object
|
||||
summary: Удалить этап линии
|
||||
tags:
|
||||
- line
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Возвращает этап линии по ID
|
||||
parameters:
|
||||
- description: ID этапа
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage'
|
||||
summary: Получить этап линии по ID
|
||||
tags:
|
||||
- line
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Обновляет данные этапа линии по ID
|
||||
parameters:
|
||||
- description: ID этапа
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- description: Данные этапа
|
||||
in: body
|
||||
name: stage
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStage'
|
||||
summary: Обновить этап линии
|
||||
tags:
|
||||
- line
|
||||
/line/stats:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Возвращает сводную статистику работы производственной линии
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.LineStats'
|
||||
summary: Получить статистику линии
|
||||
tags:
|
||||
- line
|
||||
/queue/next:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Получает и удаляет из очереди следующую задачу (FIFO)
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Получить следующую задачу из очереди
|
||||
tags:
|
||||
- queue
|
||||
/sensors/readings:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Создаёт новое показание датчика для этапа линии
|
||||
parameters:
|
||||
- description: Данные показания
|
||||
in: body
|
||||
name: reading
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.SensorReading'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.SensorReading'
|
||||
summary: Создать показание датчика
|
||||
tags:
|
||||
- sensors
|
||||
/sensors/readings/stage/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Возвращает последние 100 показаний датчиков для указанного этапа
|
||||
parameters:
|
||||
- description: ID этапа
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_rostpoliplast_backend_internal_storage.SensorReading'
|
||||
type: array
|
||||
summary: Получить показания датчиков по этапу
|
||||
tags:
|
||||
- sensors
|
||||
securityDefinitions:
|
||||
Bearer:
|
||||
description: Type "Bearer" followed by a space and the JWT token.
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type BaleTypeHandlers struct {
|
||||
Repo *storage.Repository
|
||||
Repo storage.RepositoryInterface
|
||||
}
|
||||
|
||||
func (h *BaleTypeHandlers) RegisterRoutes(g *gin.RouterGroup) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type BaleHandlers struct {
|
||||
Repo *storage.Repository
|
||||
Repo storage.RepositoryInterface
|
||||
MQ *mq.RabbitMQ
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/rostpoliplast/backend/internal/storage"
|
||||
)
|
||||
|
||||
type LineHandlers struct {
|
||||
Repo storage.RepositoryInterface
|
||||
}
|
||||
|
||||
func (h *LineHandlers) RegisterRoutes(g *gin.RouterGroup) {
|
||||
line := g.Group("/line")
|
||||
{
|
||||
line.GET("/stages", h.GetLineStages)
|
||||
line.GET("/stages/:id", h.GetLineStageByID)
|
||||
line.POST("/stages", h.CreateLineStage)
|
||||
line.PUT("/stages/:id", h.UpdateLineStage)
|
||||
line.DELETE("/stages/:id", h.DeleteLineStage)
|
||||
}
|
||||
|
||||
sensors := g.Group("/sensors")
|
||||
{
|
||||
sensors.POST("/readings", h.CreateSensorReading)
|
||||
sensors.GET("/readings/stage/:id", h.GetSensorReadingsByStage)
|
||||
}
|
||||
|
||||
events := g.Group("/events")
|
||||
{
|
||||
events.POST("", h.CreateProductionEvent)
|
||||
events.GET("/stage/:id", h.GetProductionEventsByStage)
|
||||
}
|
||||
|
||||
g.GET("/line/stats", h.GetLineStats)
|
||||
}
|
||||
|
||||
// GetLineStages Получить все этапы линии
|
||||
// @Summary Получить все этапы линии
|
||||
// @Description Возвращает список всех этапов производственной линии
|
||||
// @Tags line
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {array} storage.LineStage
|
||||
// @Router /line/stages [get]
|
||||
func (h *LineHandlers) GetLineStages(c *gin.Context) {
|
||||
stages, err := h.Repo.GetLineStages(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, stages)
|
||||
}
|
||||
|
||||
// GetLineStageByID Получить этап линии по ID
|
||||
// @Summary Получить этап линии по ID
|
||||
// @Description Возвращает этап линии по ID
|
||||
// @Tags line
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "ID этапа"
|
||||
// @Success 200 {object} storage.LineStage
|
||||
// @Router /line/stages/{id} [get]
|
||||
func (h *LineHandlers) GetLineStageByID(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
stage, err := h.Repo.GetLineStageByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, stage)
|
||||
}
|
||||
|
||||
// CreateLineStage Создать этап линии
|
||||
// @Summary Создать этап линии
|
||||
// @Description Создаёт новый этап производственной линии
|
||||
// @Tags line
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param stage body storage.LineStage true "Данные этапа"
|
||||
// @Success 201 {object} storage.LineStage
|
||||
// @Router /line/stages [post]
|
||||
func (h *LineHandlers) CreateLineStage(c *gin.Context) {
|
||||
var input storage.LineStage
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
stage, err := h.Repo.CreateLineStage(c.Request.Context(), input)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, stage)
|
||||
}
|
||||
|
||||
// UpdateLineStage Обновить этап линии
|
||||
// @Summary Обновить этап линии
|
||||
// @Description Обновляет данные этапа линии по ID
|
||||
// @Tags line
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "ID этапа"
|
||||
// @Param stage body storage.LineStage true "Данные этапа"
|
||||
// @Success 200 {object} storage.LineStage
|
||||
// @Router /line/stages/{id} [put]
|
||||
func (h *LineHandlers) UpdateLineStage(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
var input storage.LineStage
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
stage, err := h.Repo.UpdateLineStage(c.Request.Context(), id, input)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, stage)
|
||||
}
|
||||
|
||||
// DeleteLineStage Удалить этап линии
|
||||
// @Summary Удалить этап линии
|
||||
// @Description Удаляет этап линии по ID
|
||||
// @Tags line
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "ID этапа"
|
||||
// @Success 200 {object} map[string]bool
|
||||
// @Router /line/stages/{id} [delete]
|
||||
func (h *LineHandlers) DeleteLineStage(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
if err := h.Repo.DeleteLineStage(c.Request.Context(), id); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"deleted": true})
|
||||
}
|
||||
|
||||
// CreateSensorReading Создать показание датчика
|
||||
// @Summary Создать показание датчика
|
||||
// @Description Создаёт новое показание датчика для этапа линии
|
||||
// @Tags sensors
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param reading body storage.SensorReading true "Данные показания"
|
||||
// @Success 201 {object} storage.SensorReading
|
||||
// @Router /sensors/readings [post]
|
||||
func (h *LineHandlers) CreateSensorReading(c *gin.Context) {
|
||||
var input storage.SensorReading
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
reading, err := h.Repo.CreateSensorReading(c.Request.Context(), input)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, reading)
|
||||
}
|
||||
|
||||
// GetSensorReadingsByStage Получить показания датчиков по этапу
|
||||
// @Summary Получить показания датчиков по этапу
|
||||
// @Description Возвращает последние 100 показаний датчиков для указанного этапа
|
||||
// @Tags sensors
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "ID этапа"
|
||||
// @Success 200 {array} storage.SensorReading
|
||||
// @Router /sensors/readings/stage/{id} [get]
|
||||
func (h *LineHandlers) GetSensorReadingsByStage(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
readings, err := h.Repo.GetSensorReadingsByStage(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, readings)
|
||||
}
|
||||
|
||||
// CreateProductionEvent Создать событие на линии
|
||||
// @Summary Создать событие на линии
|
||||
// @Description Создаёт новое событие на этапе производственной линии
|
||||
// @Tags events
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param event body storage.ProductionEvent true "Данные события"
|
||||
// @Success 201 {object} storage.ProductionEvent
|
||||
// @Router /events [post]
|
||||
func (h *LineHandlers) CreateProductionEvent(c *gin.Context) {
|
||||
var input storage.ProductionEvent
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
event, err := h.Repo.CreateProductionEvent(c.Request.Context(), input)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, event)
|
||||
}
|
||||
|
||||
// GetProductionEventsByStage Получить события по этапу
|
||||
// @Summary Получить события по этапу
|
||||
// @Description Возвращает последние 100 событий для указанного этапа
|
||||
// @Tags events
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "ID этапа"
|
||||
// @Success 200 {array} storage.ProductionEvent
|
||||
// @Router /events/stage/{id} [get]
|
||||
func (h *LineHandlers) GetProductionEventsByStage(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
events, err := h.Repo.GetProductionEventsByStage(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, events)
|
||||
}
|
||||
|
||||
// GetLineStats Получить статистику линии
|
||||
// @Summary Получить статистику линии
|
||||
// @Description Возвращает сводную статистику работы производственной линии
|
||||
// @Tags line
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} storage.LineStats
|
||||
// @Router /line/stats [get]
|
||||
func (h *LineHandlers) GetLineStats(c *gin.Context) {
|
||||
stats, err := h.Repo.GetLineStats(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, stats)
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/rostpoliplast/backend/internal/storage"
|
||||
)
|
||||
|
||||
type testRepo struct {
|
||||
baleTypes []storage.BaleType
|
||||
bales []storage.Bale
|
||||
stages []storage.LineStage
|
||||
readings []storage.SensorReading
|
||||
events []storage.ProductionEvent
|
||||
stats *storage.LineStats
|
||||
}
|
||||
|
||||
func (r *testRepo) GetBales(ctx context.Context) ([]storage.Bale, error) {
|
||||
return r.bales, nil
|
||||
}
|
||||
func (r *testRepo) GetBaleByID(ctx context.Context, id string) (*storage.Bale, error) {
|
||||
for _, b := range r.bales {
|
||||
if b.ID == 1 {
|
||||
return &b, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
func (r *testRepo) CreateBale(ctx context.Context, input storage.Bale) (*storage.Bale, error) {
|
||||
b := storage.Bale{ID: 1, TypeID: input.TypeID, Timestamp: time.Now()}
|
||||
r.bales = append(r.bales, b)
|
||||
return &b, nil
|
||||
}
|
||||
func (r *testRepo) UpdateBale(ctx context.Context, id string, input storage.Bale) (*storage.Bale, error) {
|
||||
return &input, nil
|
||||
}
|
||||
func (r *testRepo) DeleteBale(ctx context.Context, id string) error {
|
||||
return nil
|
||||
}
|
||||
func (r *testRepo) GetBaleTypeByType(ctx context.Context, typeName string) (*storage.BaleType, error) {
|
||||
for _, bt := range r.baleTypes {
|
||||
if bt.Type == typeName {
|
||||
return &bt, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *testRepo) GetBaleTypes(ctx context.Context) ([]storage.BaleType, error) {
|
||||
return r.baleTypes, nil
|
||||
}
|
||||
func (r *testRepo) GetBaleTypeByID(ctx context.Context, id int) (*storage.BaleType, error) {
|
||||
for _, bt := range r.baleTypes {
|
||||
if bt.ID == id {
|
||||
return &bt, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
func (r *testRepo) CreateBaleType(ctx context.Context, input storage.BaleType) (*storage.BaleType, error) {
|
||||
bt := storage.BaleType{ID: 1, Type: input.Type, Weight: input.Weight}
|
||||
r.baleTypes = append(r.baleTypes, bt)
|
||||
return &bt, nil
|
||||
}
|
||||
func (r *testRepo) UpdateBaleType(ctx context.Context, id int, input storage.BaleType) (*storage.BaleType, error) {
|
||||
return &input, nil
|
||||
}
|
||||
func (r *testRepo) DeleteBaleType(ctx context.Context, id int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *testRepo) GetLineStages(ctx context.Context) ([]storage.LineStage, error) {
|
||||
return r.stages, nil
|
||||
}
|
||||
func (r *testRepo) GetLineStageByID(ctx context.Context, id int) (*storage.LineStage, error) {
|
||||
for _, s := range r.stages {
|
||||
if s.ID == id {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
func (r *testRepo) CreateLineStage(ctx context.Context, input storage.LineStage) (*storage.LineStage, error) {
|
||||
s := storage.LineStage{ID: 1, Name: input.Name, Order: input.Order}
|
||||
r.stages = append(r.stages, s)
|
||||
return &s, nil
|
||||
}
|
||||
func (r *testRepo) UpdateLineStage(ctx context.Context, id int, input storage.LineStage) (*storage.LineStage, error) {
|
||||
return &input, nil
|
||||
}
|
||||
func (r *testRepo) DeleteLineStage(ctx context.Context, id int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *testRepo) CreateSensorReading(ctx context.Context, input storage.SensorReading) (*storage.SensorReading, error) {
|
||||
s := storage.SensorReading{ID: 1, StageID: input.StageID, Value: input.Value}
|
||||
r.readings = append(r.readings, s)
|
||||
return &s, nil
|
||||
}
|
||||
func (r *testRepo) GetSensorReadingsByStage(ctx context.Context, stageID int) ([]storage.SensorReading, error) {
|
||||
var result []storage.SensorReading
|
||||
for _, s := range r.readings {
|
||||
if s.StageID == stageID {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *testRepo) CreateProductionEvent(ctx context.Context, input storage.ProductionEvent) (*storage.ProductionEvent, error) {
|
||||
e := storage.ProductionEvent{ID: 1, StageID: input.StageID, EventType: input.EventType}
|
||||
r.events = append(r.events, e)
|
||||
return &e, nil
|
||||
}
|
||||
func (r *testRepo) GetProductionEventsByStage(ctx context.Context, stageID int) ([]storage.ProductionEvent, error) {
|
||||
var result []storage.ProductionEvent
|
||||
for _, e := range r.events {
|
||||
if e.StageID == stageID {
|
||||
result = append(result, e)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *testRepo) GetLineStats(ctx context.Context) (*storage.LineStats, error) {
|
||||
if r.stats != nil {
|
||||
return r.stats, nil
|
||||
}
|
||||
return &storage.LineStats{TotalItems: 42, RejectedItems: 2, Status: "running"}, nil
|
||||
}
|
||||
|
||||
func setupRouter(repo storage.RepositoryInterface) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
v1 := r.Group("/api/v1")
|
||||
|
||||
lineH := &LineHandlers{Repo: repo}
|
||||
baleH := &BaleHandlers{Repo: repo, MQ: nil}
|
||||
baleTypeH := &BaleTypeHandlers{Repo: repo}
|
||||
|
||||
lineH.RegisterRoutes(v1)
|
||||
baleH.RegisterRoutes(v1)
|
||||
baleTypeH.RegisterRoutes(v1)
|
||||
return r
|
||||
}
|
||||
|
||||
func TestGetBales(t *testing.T) {
|
||||
repo := &testRepo{
|
||||
bales: []storage.Bale{{ID: 1, TypeID: 1, Type: "test", Timestamp: time.Now()}},
|
||||
}
|
||||
r := setupRouter(repo)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/v1/bales", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
var resp []storage.Bale
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
if len(resp) != 1 {
|
||||
t.Errorf("expected 1 bale, got %d", len(resp))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateBale(t *testing.T) {
|
||||
repo := &testRepo{}
|
||||
r := setupRouter(repo)
|
||||
|
||||
body := bytes.NewBufferString(`{"typeId": 1}`)
|
||||
req := httptest.NewRequest("POST", "/api/v1/bales", body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("expected %d, got %d", http.StatusCreated, w.Code)
|
||||
}
|
||||
|
||||
var resp storage.Bale
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
if resp.TypeID != 1 {
|
||||
t.Errorf("expected typeId 1, got %d", resp.TypeID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBalesWithTypeQuery(t *testing.T) {
|
||||
repo := &testRepo{
|
||||
baleTypes: []storage.BaleType{{ID: 1, Type: "standard"}},
|
||||
}
|
||||
r := setupRouter(repo)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/v1/bales?type=standard", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("expected %d, got %d", http.StatusCreated, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBaleTypes(t *testing.T) {
|
||||
repo := &testRepo{
|
||||
baleTypes: []storage.BaleType{{ID: 1, Type: "PET", Weight: 10.0}},
|
||||
}
|
||||
r := setupRouter(repo)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/v1/bale-types", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
var resp []storage.BaleType
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
if len(resp) != 1 {
|
||||
t.Errorf("expected 1 baleType, got %d", len(resp))
|
||||
}
|
||||
if resp[0].Type != "PET" {
|
||||
t.Errorf("expected type 'PET', got '%s'", resp[0].Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateBaleType(t *testing.T) {
|
||||
repo := &testRepo{}
|
||||
r := setupRouter(repo)
|
||||
|
||||
body := bytes.NewBufferString(`{"type": "HDPE", "weight": 15.5, "height": 100, "width": 80, "length": 120}`)
|
||||
req := httptest.NewRequest("POST", "/api/v1/bale-types", body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("expected %d, got %d", http.StatusCreated, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLineStages(t *testing.T) {
|
||||
repo := &testRepo{
|
||||
stages: []storage.LineStage{
|
||||
{ID: 1, Name: "Разгрузка", Order: 1, Equipment: "Рампа", MQTTTopic: "/sensor/weight"},
|
||||
{ID: 2, Name: "QR Сортировка", Order: 2, Equipment: "Camera", MQTTTopic: "/qr/result"},
|
||||
},
|
||||
}
|
||||
r := setupRouter(repo)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/v1/line/stages", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
var resp []storage.LineStage
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
if len(resp) != 2 {
|
||||
t.Errorf("expected 2 stages, got %d", len(resp))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateLineStage(t *testing.T) {
|
||||
repo := &testRepo{}
|
||||
r := setupRouter(repo)
|
||||
|
||||
body := bytes.NewBufferString(`{"name": "Тестовый этап", "description": "Описание", "order": 99, "equipment": "Оборудование", "mqtt_topic": "/test/topic"}`)
|
||||
req := httptest.NewRequest("POST", "/api/v1/line/stages", body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("expected %d, got %d", http.StatusCreated, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSensorReading(t *testing.T) {
|
||||
repo := &testRepo{}
|
||||
r := setupRouter(repo)
|
||||
|
||||
body := bytes.NewBufferString(`{"stage_id": 1, "sensor": "vibration", "value": 42.5, "unit": "mm/s"}`)
|
||||
req := httptest.NewRequest("POST", "/api/v1/sensors/readings", body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("expected %d, got %d", http.StatusCreated, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSensorReadingsByStage(t *testing.T) {
|
||||
repo := &testRepo{
|
||||
readings: []storage.SensorReading{
|
||||
{ID: 1, StageID: 1, Sensor: "vibration", Value: 42.5, Unit: "mm/s", Timestamp: time.Now()},
|
||||
},
|
||||
}
|
||||
r := setupRouter(repo)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/v1/sensors/readings/stage/1", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateProductionEvent(t *testing.T) {
|
||||
repo := &testRepo{}
|
||||
r := setupRouter(repo)
|
||||
|
||||
body := bytes.NewBufferString(`{"stage_id": 1, "event_type": "item_processed", "data": "success"}`)
|
||||
req := httptest.NewRequest("POST", "/api/v1/events", body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("expected %d, got %d", http.StatusCreated, w.Code)
|
||||
t.Logf("Response: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProductionEventsByStage(t *testing.T) {
|
||||
repo := &testRepo{
|
||||
events: []storage.ProductionEvent{
|
||||
{ID: 1, StageID: 1, EventType: "item_processed", Data: "success", Timestamp: time.Now()},
|
||||
},
|
||||
}
|
||||
r := setupRouter(repo)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/v1/events/stage/1", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLineStats(t *testing.T) {
|
||||
repo := &testRepo{
|
||||
stats: &storage.LineStats{
|
||||
TotalItems: 100,
|
||||
RejectedItems: 5,
|
||||
AvgSpeed: 1.5,
|
||||
CurrentStage: "Экструзия",
|
||||
Status: "running",
|
||||
},
|
||||
}
|
||||
r := setupRouter(repo)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/v1/line/stats", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
var resp storage.LineStats
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
if resp.TotalItems != 100 {
|
||||
t.Errorf("expected totalItems 100, got %d", resp.TotalItems)
|
||||
}
|
||||
if resp.RejectedItems != 5 {
|
||||
t.Errorf("expected rejectedItems 5, got %d", resp.RejectedItems)
|
||||
}
|
||||
if resp.Status != "running" {
|
||||
t.Errorf("expected status 'running', got '%s'", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateBaleType(t *testing.T) {
|
||||
repo := &testRepo{}
|
||||
r := setupRouter(repo)
|
||||
|
||||
body := bytes.NewBufferString(`{"type": "UPDATED", "weight": 20.0}`)
|
||||
req := httptest.NewRequest("PUT", "/api/v1/bale-types/1", body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteBaleType(t *testing.T) {
|
||||
repo := &testRepo{}
|
||||
r := setupRouter(repo)
|
||||
|
||||
req := httptest.NewRequest("DELETE", "/api/v1/bale-types/1", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidIDReturns400(t *testing.T) {
|
||||
repo := &testRepo{}
|
||||
r := setupRouter(repo)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/v1/line/stages/abc", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected %d, got %d", http.StatusBadRequest, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealthEndpoint(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
r.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"status": "ok"})
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/health", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
}
|
||||
@@ -28,4 +28,9 @@ func (h *Handlers) RegisterRoutes(r *gin.RouterGroup) {
|
||||
MQ: h.MQ,
|
||||
}
|
||||
queueHandlers.RegisterRoutes(r)
|
||||
|
||||
lineHandlers := &LineHandlers{
|
||||
Repo: h.Repo,
|
||||
}
|
||||
lineHandlers.RegisterRoutes(r)
|
||||
}
|
||||
|
||||
@@ -6,10 +6,36 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type RepositoryInterface interface {
|
||||
GetBales(ctx context.Context) ([]Bale, error)
|
||||
GetBaleByID(ctx context.Context, id string) (*Bale, error)
|
||||
CreateBale(ctx context.Context, input Bale) (*Bale, error)
|
||||
UpdateBale(ctx context.Context, id string, input Bale) (*Bale, error)
|
||||
DeleteBale(ctx context.Context, id string) error
|
||||
GetBaleTypeByType(ctx context.Context, typeName string) (*BaleType, error)
|
||||
GetBaleTypes(ctx context.Context) ([]BaleType, error)
|
||||
GetBaleTypeByID(ctx context.Context, id int) (*BaleType, error)
|
||||
CreateBaleType(ctx context.Context, input BaleType) (*BaleType, error)
|
||||
UpdateBaleType(ctx context.Context, id int, input BaleType) (*BaleType, error)
|
||||
DeleteBaleType(ctx context.Context, id int) error
|
||||
GetLineStages(ctx context.Context) ([]LineStage, error)
|
||||
GetLineStageByID(ctx context.Context, id int) (*LineStage, error)
|
||||
CreateLineStage(ctx context.Context, input LineStage) (*LineStage, error)
|
||||
UpdateLineStage(ctx context.Context, id int, input LineStage) (*LineStage, error)
|
||||
DeleteLineStage(ctx context.Context, id int) error
|
||||
CreateSensorReading(ctx context.Context, input SensorReading) (*SensorReading, error)
|
||||
GetSensorReadingsByStage(ctx context.Context, stageID int) ([]SensorReading, error)
|
||||
CreateProductionEvent(ctx context.Context, input ProductionEvent) (*ProductionEvent, error)
|
||||
GetProductionEventsByStage(ctx context.Context, stageID int) ([]ProductionEvent, error)
|
||||
GetLineStats(ctx context.Context) (*LineStats, error)
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
var _ RepositoryInterface = (*Repository)(nil)
|
||||
|
||||
func NewRepository(pool *pgxpool.Pool) *Repository {
|
||||
return &Repository{
|
||||
pool: pool,
|
||||
@@ -151,3 +177,137 @@ func (r *Repository) DeleteBaleType(ctx context.Context, id int) error {
|
||||
_, err := r.pool.Exec(ctx, "DELETE FROM bale_types WHERE id = $1", id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Repository) GetLineStages(ctx context.Context) ([]LineStage, error) {
|
||||
rows, err := r.pool.Query(ctx, "SELECT id, name, description, stage_order, equipment, mqtt_topic FROM line_stages ORDER BY stage_order")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var stages []LineStage
|
||||
for rows.Next() {
|
||||
var s LineStage
|
||||
if err := rows.Scan(&s.ID, &s.Name, &s.Description, &s.Order, &s.Equipment, &s.MQTTTopic); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stages = append(stages, s)
|
||||
}
|
||||
return stages, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetLineStageByID(ctx context.Context, id int) (*LineStage, error) {
|
||||
var s LineStage
|
||||
err := r.pool.QueryRow(ctx, "SELECT id, name, description, stage_order, equipment, mqtt_topic FROM line_stages WHERE id = $1", id).Scan(&s.ID, &s.Name, &s.Description, &s.Order, &s.Equipment, &s.MQTTTopic)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateLineStage(ctx context.Context, input LineStage) (*LineStage, error) {
|
||||
var s LineStage
|
||||
err := r.pool.QueryRow(ctx, `
|
||||
INSERT INTO line_stages (name, description, stage_order, equipment, mqtt_topic)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, name, description, stage_order, equipment, mqtt_topic`, input.Name, input.Description, input.Order, input.Equipment, input.MQTTTopic).Scan(&s.ID, &s.Name, &s.Description, &s.Order, &s.Equipment, &s.MQTTTopic)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateLineStage(ctx context.Context, id int, input LineStage) (*LineStage, error) {
|
||||
var s LineStage
|
||||
err := r.pool.QueryRow(ctx, `
|
||||
UPDATE line_stages SET name = $1, description = $2, stage_order = $3, equipment = $4, mqtt_topic = $5 WHERE id = $6
|
||||
RETURNING id, name, description, stage_order, equipment, mqtt_topic`, input.Name, input.Description, input.Order, input.Equipment, input.MQTTTopic, id).Scan(&s.ID, &s.Name, &s.Description, &s.Order, &s.Equipment, &s.MQTTTopic)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteLineStage(ctx context.Context, id int) error {
|
||||
_, err := r.pool.Exec(ctx, "DELETE FROM line_stages WHERE id = $1", id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Repository) CreateSensorReading(ctx context.Context, input SensorReading) (*SensorReading, error) {
|
||||
var s SensorReading
|
||||
err := r.pool.QueryRow(ctx, `
|
||||
INSERT INTO sensor_readings (stage_id, sensor, value, unit, timestamp)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, stage_id, sensor, value, unit, timestamp`, input.StageID, input.Sensor, input.Value, input.Unit, input.Timestamp).Scan(&s.ID, &s.StageID, &s.Sensor, &s.Value, &s.Unit, &s.Timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetSensorReadingsByStage(ctx context.Context, stageID int) ([]SensorReading, error) {
|
||||
rows, err := r.pool.Query(ctx, "SELECT id, stage_id, sensor, value, unit, timestamp FROM sensor_readings WHERE stage_id = $1 ORDER BY timestamp DESC LIMIT 100", stageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var readings []SensorReading
|
||||
for rows.Next() {
|
||||
var r SensorReading
|
||||
if err := rows.Scan(&r.ID, &r.StageID, &r.Sensor, &r.Value, &r.Unit, &r.Timestamp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readings = append(readings, r)
|
||||
}
|
||||
return readings, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateProductionEvent(ctx context.Context, input ProductionEvent) (*ProductionEvent, error) {
|
||||
var e ProductionEvent
|
||||
err := r.pool.QueryRow(ctx, `
|
||||
INSERT INTO production_events (stage_id, event_type, data, timestamp)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, stage_id, event_type, data, timestamp`, input.StageID, input.EventType, input.Data, input.Timestamp).Scan(&e.ID, &e.StageID, &e.EventType, &e.Data, &e.Timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetProductionEventsByStage(ctx context.Context, stageID int) ([]ProductionEvent, error) {
|
||||
rows, err := r.pool.Query(ctx, "SELECT id, stage_id, event_type, data, timestamp FROM production_events WHERE stage_id = $1 ORDER BY timestamp DESC LIMIT 100", stageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var events []ProductionEvent
|
||||
for rows.Next() {
|
||||
var e ProductionEvent
|
||||
if err := rows.Scan(&e.ID, &e.StageID, &e.EventType, &e.Data, &e.Timestamp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events = append(events, e)
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetLineStats(ctx context.Context) (*LineStats, error) {
|
||||
var stats LineStats
|
||||
err := r.pool.QueryRow(ctx, `
|
||||
SELECT
|
||||
COUNT(CASE WHEN pe.event_type = 'item_processed' THEN 1 END) as total_items,
|
||||
COUNT(CASE WHEN pe.event_type = 'item_rejected' THEN 1 END) as rejected_items,
|
||||
COALESCE(AVG(sr.value) FILTER (WHERE sr.sensor = 'conveyor_speed'), 0) as avg_speed,
|
||||
COALESCE((SELECT ls.name FROM line_stages ls ORDER BY ls.stage_order LIMIT 1), '') as current_stage,
|
||||
'running' as status,
|
||||
NOW() as last_update_time
|
||||
FROM production_events pe
|
||||
LEFT JOIN sensor_readings sr ON pe.stage_id = sr.stage_id
|
||||
`).Scan(&stats.TotalItems, &stats.RejectedItems, &stats.AvgSpeed, &stats.CurrentStage, &stats.Status, &stats.LastUpdateTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
@@ -24,6 +24,49 @@ type Bale struct {
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// LineStage Этап производственной линии
|
||||
// @Description Этап производственной линии переработки
|
||||
type LineStage struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Order int `json:"order"`
|
||||
Equipment string `json:"equipment"`
|
||||
MQTTTopic string `json:"mqtt_topic"`
|
||||
}
|
||||
|
||||
// SensorReading Показание датчика
|
||||
// @Description Показание датчика на этапе линии
|
||||
type SensorReading struct {
|
||||
ID int `json:"id"`
|
||||
StageID int `json:"stage_id"`
|
||||
Sensor string `json:"sensor"`
|
||||
Value float64 `json:"value"`
|
||||
Unit string `json:"unit"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// ProductionEvent Событие на линии
|
||||
// @Description Событие, произошедшее на этапе производственной линии
|
||||
type ProductionEvent struct {
|
||||
ID int `json:"id"`
|
||||
StageID int `json:"stage_id"`
|
||||
EventType string `json:"event_type"`
|
||||
Data string `json:"data"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// LineStats Статистика линии
|
||||
// @Description Сводная статистика работы производственной линии
|
||||
type LineStats struct {
|
||||
TotalItems int `json:"total_items"`
|
||||
RejectedItems int `json:"rejected_items"`
|
||||
AvgSpeed float64 `json:"avg_speed"`
|
||||
CurrentStage string `json:"current_stage"`
|
||||
Status string `json:"status"`
|
||||
LastUpdateTime time.Time `json:"last_update_time"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE line_stages (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
stage_order INT NOT NULL UNIQUE,
|
||||
equipment VARCHAR(200),
|
||||
mqtt_topic VARCHAR(200)
|
||||
);
|
||||
|
||||
CREATE TABLE sensor_readings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
stage_id INT REFERENCES line_stages(id) ON DELETE CASCADE,
|
||||
sensor VARCHAR(100) NOT NULL,
|
||||
value DOUBLE PRECISION NOT NULL,
|
||||
unit VARCHAR(20),
|
||||
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE production_events (
|
||||
id SERIAL PRIMARY KEY,
|
||||
stage_id INT REFERENCES line_stages(id) ON DELETE CASCADE,
|
||||
event_type VARCHAR(100) NOT NULL,
|
||||
data TEXT,
|
||||
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Индексы для производительности
|
||||
CREATE INDEX idx_sensor_readings_stage ON sensor_readings(stage_id, timestamp DESC);
|
||||
CREATE INDEX idx_production_events_stage ON production_events(stage_id, timestamp DESC);
|
||||
|
||||
-- Начальные данные этапов линии
|
||||
INSERT INTO line_stages (name, description, stage_order, equipment, mqtt_topic) VALUES
|
||||
('Разгрузка', 'Приемка лома, взвешивание', 1, 'Рампа, весы', '/sensor/weight/party'),
|
||||
('QR Сортировка', 'Сканирование QR-кодов', 2, 'Camera Module v3', '/qr/result'),
|
||||
('Подача', 'Загрузка на конвейер', 3, 'Danfoss VLT FC 302', '/drive/telemetry'),
|
||||
('Дробление', 'Шредер / Дробилка', 4, 'SEW MOVITRAC', '/drive/vibration'),
|
||||
('Мойка', 'Очистка, флотация', 5, 'Ванна, насосы', '/sensor/level'),
|
||||
('Сушка', 'Удаление влаги', 6, 'Центрифуга', '/sensor/dry/temp'),
|
||||
('Экструзия', 'Плавление, грануляция', 7, 'ABB ACS880', '/extruder/pressure'),
|
||||
('Манипулятор', 'Упаковка, паллетирование', 8, 'Робот-упаковщик', '/robot/cycle');
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE IF EXISTS production_events;
|
||||
DROP TABLE IF EXISTS sensor_readings;
|
||||
DROP TABLE IF EXISTS line_stages;
|
||||
-- +goose StatementEnd
|
||||
Reference in New Issue
Block a user