{"openapi":"3.1.0","info":{"title":"Modbus Sandbox API","description":"REST API Modbus Sandbox - публичного тестового Modbus-TCP сервера. Сервер бесплатно эмулирует 10 устройств с unit ID от 1 до 10. Значения регистров живут в памяти и общие для всех подключённых пользователей, перезапуск сервера всё сбрасывает.","version":"0.1.0"},"paths":{"/api/modbus/behaviors":{"get":{"tags":["Поведения"],"summary":"Список поведений","description":"Показывает поведение каждой ячейки: слейв, область и адрес. Можно отфильтровать по слейву или области и оставить только переопределённые вручную.","operationId":"get_behaviors_api_modbus_behaviors_get","parameters":[{"name":"slave","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":10,"minimum":1},{"type":"null"}],"title":"Slave"}},{"name":"area","in":"query","required":false,"schema":{"anyOf":[{"enum":["cl","di","hr","ir"],"type":"string"},{"type":"null"}],"title":"Area"}},{"name":"only_overrides","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Only Overrides"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BehaviorsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/behaviors/{slave}/{area}/{address}":{"put":{"tags":["Поведения"],"summary":"Настроить поведение","description":"Назначает поведение одной ячейке, например счётчик или синус для регистра. Параметры приводятся к формату регистра, ошибка в данных возвращает 400. Действие из интерфейса ограничено лимитом на IP.","operationId":"put_behavior_api_modbus_behaviors__slave___area___address__put","parameters":[{"name":"slave","in":"path","required":true,"schema":{"type":"integer","maximum":10,"minimum":1,"title":"Slave"}},{"name":"area","in":"path","required":true,"schema":{"enum":["cl","di","hr","ir"],"type":"string","title":"Area"}},{"name":"address","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Address"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BehaviorIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BehaviorOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/behaviors/batch":{"post":{"tags":["Поведения"],"summary":"Настроить много сразу","description":"За один запрос назначает или сбрасывает поведения нескольких ячеек. Сначала проверяются все элементы: если хоть один неверный, ничего не меняется и возвращается 400.","operationId":"post_behaviors_batch_api_modbus_behaviors_batch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/behaviors/reset":{"post":{"tags":["Поведения"],"summary":"Сбросить поведения","description":"Привязки снова становятся дефолтными: все сразу или только одного слейва. В ответе число сброшенных.","operationId":"post_behaviors_reset_api_modbus_behaviors_reset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/state":{"get":{"tags":["Значения"],"summary":"Значения слейва","description":"Текущие значения регистров и битов слейва, уже декодированные по его формату. Область можно не указывать: вернутся все, которые есть у слейва (у slave 10 это только ir).","operationId":"get_state_api_modbus_state_get","parameters":[{"name":"slave","in":"query","required":true,"schema":{"type":"integer","maximum":10,"minimum":1,"title":"Slave"}},{"name":"area","in":"query","required":false,"schema":{"anyOf":[{"enum":["cl","di","hr","ir"],"type":"string"},{"type":"null"}],"title":"Area"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/write":{"post":{"tags":["Значения"],"summary":"Записать значение","description":"Записывает значение в coil (cl) или holding register (hr), как это делает редактор в интерфейсе. Запись в закрытый регистр отклоняется с кодом 409. Действие ограничено лимитом запросов из интерфейса на IP.","operationId":"post_write_api_modbus_write_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/snapshot":{"post":{"tags":["Снимки"],"summary":"Снять снимок","description":"Снимает состояние всех слейвов одним JSON-ответом. Сервер снимки не хранит: сохраните его у себя, чтобы потом сравнить или восстановить.","operationId":"post_snapshot_api_modbus_snapshot_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}}}}}},"/api/modbus/snapshot/diff":{"post":{"tags":["Снимки"],"summary":"Сравнить снимки","description":"Показывает различия двух снимков, до и после: изменившиеся, добавившиеся и пропавшие значения. Совпавшие значения в ответ не попадают.","operationId":"post_snapshot_diff_api_modbus_snapshot_diff_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotDiffIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotDiffResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/restore":{"post":{"tags":["Снимки"],"summary":"Восстановить снимок","description":"Записывает значения из снимка обратно в coils и holding registers. Значения ir/di игнорируются, закрытые регистры пропускаются. Снимок полностью проверяется перед записью: любая ошибка возвращает 400 и ничего не пишется.","operationId":"post_restore_api_modbus_restore_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestoreResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/journal":{"get":{"tags":["Журнал"],"summary":"События журнала","description":"Последние события сервера от старых к новым: записи значений, смена поведений, инъекции и попытки записать в закрытые регистры. Хранятся только в памяти.","operationId":"get_journal_api_modbus_journal_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JournalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/inject":{"get":{"tags":["Инъекции"],"summary":"Активные инъекции","description":"Список действующих инъекций ошибок с остатком времени жизни. Истёкшие в список не попадают.","operationId":"get_inject_api_modbus_inject_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InjectionsResponse"}}}}}},"post":{"tags":["Инъекции"],"summary":"Добавить инъекцию","description":"Заставляет слейв отвечать кодом ошибки, задерживать ответ или молчать: так проверяется реакция клиента на сбои. Время жизни ограничено, дальше инъекция гаснет сама.","operationId":"post_inject_api_modbus_inject_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InjectionIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InjectionOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/inject/clear":{"post":{"tags":["Инъекции"],"summary":"Снять инъекции","description":"Отключает инъекции: одну по номеру, все инъекции слейва или все сразу. В ответе число снятых.","operationId":"post_inject_clear_api_modbus_inject_clear_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InjectClearIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InjectClearResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/ping":{"post":{"tags":["Значения"],"summary":"Проверить соединение","description":"Сервер сам делает несколько контрольных чтений выбранного слейва и возвращает задержки ответа (минимальная, средняя, максимальная) и число успешных и неудачных попыток. Регистры не меняются.","operationId":"post_ping_api_modbus_ping_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PingIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/convert":{"post":{"tags":["Инструменты"],"summary":"Конвертировать значение","description":"Переводит значение в пару регистров или пару регистров в значение во всех четырёх порядках слов: ABCD, CDAB, BADC, DCBA. Пересчёт использует тот же кодек, что и слейвы 2..9.","operationId":"post_convert_api_modbus_convert_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConvertIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConvertResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/configgen":{"post":{"tags":["Инструменты"],"summary":"Сгенерировать конфиг","description":"Готовит настройки подключения к серверу для конкретного инструмента: Modbus Poll, QModMaster, CAS Modbus Scanner, pyModbus или Node-RED.","operationId":"post_configgen_api_modbus_configgen_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigGenIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigGenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modbus/metrics":{"get":{"tags":["Служебное"],"summary":"Счётчики сервера","description":"Число активных соединений, всего запросов и ошибок, скорости за последнее окно и время работы сервера.","operationId":"get_metrics_api_modbus_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricsResponse"}}}}}}},"/healthz":{"get":{"tags":["Служебное"],"summary":"Проверка здоровья","description":"Простая проверка для мониторинга и балансировщика: отвечает, пока процесс жив.","operationId":"get_healthz_healthz_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthzResponse"}}}}}}},"/metrics":{"get":{"tags":["Служебное"],"summary":"Метрики Prometheus","description":"Те же счётчики сервера в текстовом формате Prometheus для систем мониторинга.","operationId":"get_prometheus_metrics_metrics_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}}},"components":{"schemas":{"BatchIn":{"properties":{"items":{"items":{"$ref":"#/components/schemas/BatchItem"},"type":"array","maxItems":2048,"title":"Items"}},"type":"object","required":["items"],"title":"BatchIn","description":"Пакет установки/сброса поведений; применяется атомарно (TASK-303).\n\nНе больше `MAX_BATCH_ITEMS` элементов (422 при переполнении)."},"BatchItem":{"properties":{"slave":{"type":"integer","title":"Slave"},"area":{"type":"string","enum":["cl","di","hr","ir"],"title":"Area"},"address":{"type":"integer","title":"Address"},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"},"params":{"additionalProperties":true,"type":"object","title":"Params","default":{}},"locked":{"type":"boolean","title":"Locked","default":false},"remove":{"type":"boolean","title":"Remove","default":false}},"type":"object","required":["slave","area","address"],"title":"BatchItem","description":"Элемент пакетной операции (TASK-303).\n\n`remove=True` — сброс привязки к дефолту (kind в этом случае запрещён),\nиначе — установка поведения (kind обязателен)."},"BatchResponse":{"properties":{"applied":{"type":"integer","title":"Applied"},"removed":{"type":"integer","title":"Removed"}},"type":"object","required":["applied","removed"],"title":"BatchResponse","description":"Итог пакета: `applied` — установлено, `removed` — сброшено к дефолтам."},"BehaviorIn":{"properties":{"kind":{"type":"string","title":"Kind"},"params":{"additionalProperties":true,"type":"object","title":"Params","default":{}},"locked":{"type":"boolean","title":"Locked","default":false}},"type":"object","required":["kind"],"title":"BehaviorIn","description":"Входное поведение для установки (TASK-302).\n\n`kind` проверяется моделью `Behavior` (400 на неизвестный), value-параметры\nприводятся к формату регистра при установке (TASK-241)."},"BehaviorOut":{"properties":{"slave":{"type":"integer","title":"Slave"},"area":{"type":"string","enum":["cl","di","hr","ir"],"title":"Area"},"address":{"type":"integer","title":"Address"},"kind":{"type":"string","title":"Kind"},"params":{"additionalProperties":true,"type":"object","title":"Params"},"locked":{"type":"boolean","title":"Locked"},"is_default":{"type":"boolean","title":"Is Default"}},"type":"object","required":["slave","area","address","kind","params","locked","is_default"],"title":"BehaviorOut","description":"Поведение привязки в REST-представлении."},"BehaviorsResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/BehaviorOut"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"overrides":{"type":"integer","title":"Overrides"}},"type":"object","required":["items","total","overrides"],"title":"BehaviorsResponse","description":"Полный конфиг поведений (TASK-301).\n\n`total` — все привязки реестра (слейвы 1..9), `overrides` — число\nпереопределённых (не дефолтных) привязок, `items` — результат фильтра."},"ConfigGenIn":{"properties":{"tool":{"type":"string","enum":["modbus_poll","qmodmaster","cas_scanner","pymodbus","nodered"],"title":"Tool"},"slave":{"type":"integer","maximum":10.0,"minimum":1.0,"title":"Slave"},"area":{"type":"string","enum":["cl","di","hr","ir"],"title":"Area"},"address":{"type":"integer","minimum":1.0,"title":"Address"},"count":{"type":"integer","maximum":1024.0,"minimum":1.0,"title":"Count","default":1},"host":{"anyOf":[{"type":"string","maxLength":253},{"type":"null"}],"title":"Host"},"port":{"anyOf":[{"type":"integer","maximum":65535.0,"minimum":1.0},{"type":"null"}],"title":"Port"}},"type":"object","required":["tool","slave","area","address"],"title":"ConfigGenIn","description":"Вход генератора конфигов клиентских инструментов (TASK-742).\n\nЦель: `slave`/`area`/`address`/`count` (количество значений; для 32-битных\nформатов — по 2 регистра на значение). `host`/`port` — необязательные\nпереопределения адреса сервера (по умолчанию — публичный хост и\n`MODBUS_TCP_PORT`)."},"ConfigGenResponse":{"properties":{"tool":{"type":"string","enum":["modbus_poll","qmodmaster","cas_scanner","pymodbus","nodered"],"title":"Tool"},"filename":{"type":"string","title":"Filename"},"content":{"type":"string","title":"Content"}},"type":"object","required":["tool","filename","content"],"title":"ConfigGenResponse","description":"Сгенерированный конфиг: имя файла для скачивания и его содержимое (TASK-742)."},"ConvertIn":{"properties":{"kind":{"type":"string","enum":["uint32","float"],"title":"Kind"},"value":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Value"},"regs":{"anyOf":[{"prefixItems":[{"type":"integer"},{"type":"integer"}],"type":"array","maxItems":2,"minItems":2},{"type":"null"}],"title":"Regs"}},"type":"object","required":["kind"],"title":"ConvertIn","description":"Вход конвертера байт/слов (TASK-741).\n\nРовно одно из полей: `value` — значение (uint32 или float) для перевода\nв регистры, `regs` — два регистра (слова) для перевода в значение.\nПорядок — все четыре: ABCD, CDAB, BADC, DCBA."},"ConvertResponse":{"properties":{"kind":{"type":"string","enum":["uint32","float"],"title":"Kind"},"rows":{"items":{"$ref":"#/components/schemas/ConvertRow"},"type":"array","title":"Rows"}},"type":"object","required":["kind","rows"],"title":"ConvertResponse","description":"Таблица конвертации по всем 4 порядкам (TASK-741).\n\nПри входе `value` регистры строк различаются (значение одно), при входе\n`regs` — различаются значения (регистры одни)."},"ConvertRow":{"properties":{"order":{"type":"string","enum":["ABCD","CDAB","BADC","DCBA"],"title":"Order"},"regs":{"prefixItems":[{"type":"integer"},{"type":"integer"}],"type":"array","maxItems":2,"minItems":2,"title":"Regs"},"value":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"string"}],"title":"Value"}},"type":"object","required":["order","regs","value"],"title":"ConvertRow","description":"Строка таблицы конвертации: значение ↔ регистры в одном порядке.\n\n`value` — строкой только NaN/inf/-inf (в JSON они не сериализуются)."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthzResponse":{"properties":{"status":{"type":"string","title":"Status"}},"type":"object","required":["status"],"title":"HealthzResponse","description":"Ответ healthcheck (TASK-310)."},"InjectClearIn":{"properties":{"id":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Id"},"slave":{"anyOf":[{"type":"integer","maximum":10.0,"minimum":1.0},{"type":"null"}],"title":"Slave"}},"type":"object","title":"InjectClearIn","description":"Снятие инъекций (TASK-309, TASK-605): по `id`, по `slave` или все."},"InjectClearResponse":{"properties":{"cleared":{"type":"integer","title":"Cleared"}},"type":"object","required":["cleared"],"title":"InjectClearResponse","description":"Число снятых инъекций."},"InjectionIn":{"properties":{"kind":{"type":"string","enum":["exception","delay","timeout"],"title":"Kind"},"slave":{"type":"integer","maximum":10.0,"minimum":1.0,"title":"Slave"},"area":{"anyOf":[{"type":"string","enum":["cl","di","hr","ir"]},{"type":"null"}],"title":"Area"},"address":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Address"},"count":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Count"},"code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Code"},"delay_ms":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Delay Ms"},"ttl_s":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Ttl S"}},"type":"object","required":["kind","slave"],"title":"InjectionIn","description":"Инъекция ошибки (TASK-309, ADR-004 п. 6).\n\n`kind`: `\"exception\"` — отвечать кодом `code` (0x01/0x02/0x03/0x04/0x06);\n`\"delay\"` — задержка `delay_ms` (0..5000) перед ответом; `\"timeout\"` — не\nотвечать `ttl_s` секунд. Цель: весь слейв либо диапазон `area`/`address`/\n`count` (по сетке значений). `ttl_s` ≤ `INJECTION_MAX_TTL_S` (ленивый\nавтосброс)."},"InjectionOut":{"properties":{"id":{"type":"integer","title":"Id"},"kind":{"type":"string","enum":["exception","delay","timeout"],"title":"Kind"},"slave":{"type":"integer","title":"Slave"},"area":{"anyOf":[{"type":"string","enum":["cl","di","hr","ir"]},{"type":"null"}],"title":"Area"},"address":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Address"},"count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Count"},"code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Code"},"delay_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Delay Ms"},"ttl_s":{"type":"number","title":"Ttl S"},"created_at":{"type":"number","title":"Created At"},"expires_at":{"type":"number","title":"Expires At"},"remaining_s":{"type":"number","title":"Remaining S"}},"type":"object","required":["id","kind","slave","area","address","count","code","delay_ms","ttl_s","created_at","expires_at","remaining_s"],"title":"InjectionOut","description":"Активная инъекция: id, цель, параметры и срок жизни (TASK-309).\n\n`remaining_s` — остаток TTL на момент ответа (TASK-743): `created_at`/\n`expires_at` — монотонические часы реестра, браузеру они бесполезны."},"InjectionsResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/InjectionOut"},"type":"array","title":"Items"},"max_ttl_s":{"type":"number","title":"Max Ttl S"}},"type":"object","required":["items","max_ttl_s"],"title":"InjectionsResponse","description":"Активные инъекции ошибок (TASK-743).\n\n`items` — в порядке добавления (без истёкших); `max_ttl_s` — предел\n`INJECTION_MAX_TTL_S` для формы UI."},"JournalEntryOut":{"properties":{"action":{"type":"string","title":"Action"},"slave":{"type":"integer","title":"Slave"},"area":{"anyOf":[{"type":"string","enum":["cl","di","hr","ir"]},{"type":"null"}],"title":"Area"},"address":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Address"},"value":{"title":"Value"},"source":{"type":"string","title":"Source"},"ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip"},"timestamp":{"type":"number","title":"Timestamp"}},"type":"object","required":["action","slave","area","address","source","timestamp"],"title":"JournalEntryOut","description":"Запись журнала (TASK-308, TASK-505).\n\n`area`/`address` — None для событий уровня слейва (например, инъекций)."},"JournalResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/JournalEntryOut"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["items","total"],"title":"JournalResponse","description":"Записи журнала от старых к новым (TASK-308).\n\n`total` — записей в буфере сейчас (с учётом вытеснения по `JOURNAL_SIZE`)."},"MetricsResponse":{"properties":{"active_connections":{"type":"integer","title":"Active Connections"},"requests_total":{"type":"integer","title":"Requests Total"},"errors_total":{"type":"integer","title":"Errors Total"},"req_per_s":{"type":"number","title":"Req Per S"},"err_per_s":{"type":"number","title":"Err Per S"},"uptime_s":{"type":"number","title":"Uptime S"}},"type":"object","required":["active_connections","requests_total","errors_total","req_per_s","err_per_s","uptime_s"],"title":"MetricsResponse","description":"Счётчики Modbus-сервера (TASK-703).\n\n`req_per_s` / `err_per_s` — скорости за скользящее окно\n(`app.modbus.metrics.RATE_WINDOW_S`), `uptime_s` — с запуска сервера."},"PingIn":{"properties":{"slave":{"type":"integer","maximum":10.0,"minimum":1.0,"title":"Slave"},"count":{"type":"integer","maximum":10.0,"minimum":1.0,"title":"Count","default":5}},"type":"object","required":["slave"],"title":"PingIn","description":"Параметры пробы соединения (TASK-702).\n\n`count` — число контрольных чтений (1..10, ограничение пробы —\n`app.modbus.ping.MAX_ATTEMPTS`)."},"PingResponse":{"properties":{"slave":{"type":"integer","title":"Slave"},"attempts":{"type":"integer","title":"Attempts"},"ok":{"type":"integer","title":"Ok"},"errors":{"type":"integer","title":"Errors"},"rtt_min_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rtt Min Ms"},"rtt_avg_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rtt Avg Ms"},"rtt_max_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rtt Max Ms"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"}},"type":"object","required":["slave","attempts","ok","errors","rtt_min_ms","rtt_avg_ms","rtt_max_ms","last_error"],"title":"PingResponse","description":"Итог пробы соединения (TASK-702).\n\n`ok` / `errors` — число успешных и неудачных попыток; `rtt_*_ms` —\nmin/avg/max по успешным ответам (None, если успехов не было);\n`last_error` — последняя ошибка попытки."},"RegisterFormat":{"type":"string","enum":["uint16","uint32_abcd","uint32_cdab","uint32_badc","uint32_dcba","float_abcd","float_cdab","float_badc","float_dcba"],"title":"RegisterFormat","description":"Формат значения регистра: тип данных + порядок байтов/слов (A — старший байт)."},"ResetIn":{"properties":{"slave":{"anyOf":[{"type":"integer","maximum":10.0,"minimum":1.0},{"type":"null"}],"title":"Slave"}},"type":"object","title":"ResetIn","description":"Сброс переопределений: без `slave` — все, иначе — одного слейва (TASK-304)."},"ResetResponse":{"properties":{"reset":{"type":"integer","title":"Reset"}},"type":"object","required":["reset"],"title":"ResetResponse","description":"Число сброшенных переопределений (дефолтные привязки не считаются)."},"RestoreResponse":{"properties":{"restored":{"type":"integer","title":"Restored"},"skipped_locked":{"type":"integer","title":"Skipped Locked"},"ignored":{"type":"integer","title":"Ignored"}},"type":"object","required":["restored","skipped_locked","ignored"],"title":"RestoreResponse","description":"Итог восстановления (TASK-307).\n\n`restored` — записано значений hr/cl; `skipped_locked` — пропущено из-за\nlocked-поведений (ADR-003 п. 3); `ignored` — значения ir/di из снимка\n(генерируются движком, не восстанавливаются)."},"Snapshot":{"properties":{"version":{"type":"integer","title":"Version","default":1},"slaves":{"additionalProperties":{"additionalProperties":{"additionalProperties":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"boolean"}]},"type":"object"},"propertyNames":{"enum":["cl","di","hr","ir"]},"type":"object"},"type":"object","title":"Slaves"}},"type":"object","required":["slaves"],"title":"Snapshot","description":"Снимок состояния слейвов (TASK-307, ADR-004 п. 3).\n\nЗначения по сетке адресов (1,3,…,1023 для 32-битных форматов),\nдекодированные по формату слейва. `version` — формат снимка (сейчас 1)."},"SnapshotDiffIn":{"properties":{"a":{"$ref":"#/components/schemas/Snapshot"},"b":{"$ref":"#/components/schemas/Snapshot"}},"type":"object","required":["a","b"],"title":"SnapshotDiffIn","description":"Пара снимков для сравнения (TASK-744): `a` — «до», `b` — «после»."},"SnapshotDiffResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/SnapshotDiffRow"},"type":"array","title":"Rows"},"same":{"type":"integer","title":"Same"}},"type":"object","required":["rows","same"],"title":"SnapshotDiffResponse","description":"Дифф снимков: только различия + число совпавших значений (TASK-744).\n\n`rows` — по порядку slave → area → address; `same` — значений, которые\nсовпали в обоих снимках (в таблицу не попадают)."},"SnapshotDiffRow":{"properties":{"slave":{"type":"integer","title":"Slave"},"area":{"type":"string","enum":["cl","di","hr","ir"],"title":"Area"},"address":{"type":"integer","title":"Address"},"a":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"boolean"},{"type":"null"}],"title":"A"},"b":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"boolean"},{"type":"null"}],"title":"B"}},"type":"object","required":["slave","area","address","a","b"],"title":"SnapshotDiffRow","description":"Одно различие снимков: значение по адресу в A и B (TASK-744).\n\n`None` — записи нет в этом снимке (значение добавилось или пропало)."},"StateResponse":{"properties":{"slave":{"type":"integer","title":"Slave"},"fmt":{"$ref":"#/components/schemas/RegisterFormat"},"areas":{"additionalProperties":{"additionalProperties":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"boolean"}]},"type":"object"},"propertyNames":{"enum":["cl","di","hr","ir"]},"type":"object","title":"Areas"}},"type":"object","required":["slave","fmt","areas"],"title":"StateResponse","description":"Снимок значений слейва (TASK-305).\n\nЗначения — по сетке адресов (1,3,…,1023 для 32-битных форматов),\nдекодированы по `fmt`; биты cl/di — bool. Исключение slave 10: IR 300 —\nодно значение UINT32_ABCD (Unix timestamp, `docs/HRIR_SPEC.md`)."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WriteIn":{"properties":{"slave":{"type":"integer","title":"Slave"},"area":{"type":"string","enum":["cl","di","hr","ir"],"title":"Area"},"address":{"type":"integer","title":"Address"},"value":{"anyOf":[{"type":"boolean"},{"type":"integer"},{"type":"number"}],"title":"Value"}},"type":"object","required":["slave","area","address","value"],"title":"WriteIn","description":"Запись значения CL/HR из UI (TASK-306).\n\n`address` — адрес сетки значений (1,3,…,1023 для 32-битных форматов);\nдля cl значение — bool, для hr — число (приводится к формату регистра)."},"WriteResponse":{"properties":{"slave":{"type":"integer","title":"Slave"},"area":{"type":"string","enum":["cl","di","hr","ir"],"title":"Area"},"address":{"type":"integer","title":"Address"},"value":{"anyOf":[{"type":"boolean"},{"type":"integer"},{"type":"number"}],"title":"Value"}},"type":"object","required":["slave","area","address","value"],"title":"WriteResponse","description":"Подтверждение записи: значение после приведения к формату (TASK-306)."}}},"tags":[{"name":"Поведения","description":"Генерация значений: счётчики, волны, шум и другие поведения."},{"name":"Значения","description":"Текущие значения слейвов, запись и проба соединения."},{"name":"Снимки","description":"Снимки состояния, сравнение и восстановление."},{"name":"Журнал","description":"События сервера: записи, поведения, инъекции."},{"name":"Инъекции","description":"Имитация сбоев устройства: ошибки, задержки, молчание."},{"name":"Инструменты","description":"Конвертер байт/слов и готовые конфиги для клиентских программ."},{"name":"Служебное","description":"Здоровье процесса и счётчики для мониторинга."}]}