{"id":50532897,"url":"https://github.com/moqui/moqui-device-gateway","last_synced_at":"2026-06-03T15:01:49.261Z","repository":{"id":360220224,"uuid":"1216248985","full_name":"moqui/moqui-device-gateway","owner":"moqui","description":"Standalone Apache Camel gateway on Quarkus that implements the same routes as the moqui-camel component as an independently deployable service","archived":false,"fork":false,"pushed_at":"2026-05-25T13:59:13.000Z","size":94,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-25T15:23:03.081Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moqui.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-20T18:06:26.000Z","updated_at":"2026-05-25T13:59:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/moqui/moqui-device-gateway","commit_stats":null,"previous_names":["moqui/moqui-device-gateway"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/moqui/moqui-device-gateway","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moqui%2Fmoqui-device-gateway","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moqui%2Fmoqui-device-gateway/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moqui%2Fmoqui-device-gateway/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moqui%2Fmoqui-device-gateway/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moqui","download_url":"https://codeload.github.com/moqui/moqui-device-gateway/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moqui%2Fmoqui-device-gateway/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33870026,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-03T02:00:06.370Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-03T15:01:48.325Z","updated_at":"2026-06-03T15:01:49.250Z","avatar_url":"https://github.com/moqui.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# moqui-device-gateway\n\n`moqui-device-gateway` is a **model-first, model-driven, and data-driven industrial edge gateway** for devices modeled with the `moqui-device` component.\n\nUnlike flow-diagram-first tools, where the developer manually builds behavior with ad hoc drag-and-drop flows, this gateway starts from a **shared, relational, industrial device model**. Devices, PLCs, parameters, requests, subscriptions, transport endpoints, gateway ownership, recipes, device configurations, and runtime activation rules are declared as structured data in the Moqui database. Apache Camel routes are then generated and activated as runtime projections of that model.\n\nThe result is a different class of gateway:\n\n- **model-first**: the `moqui-device`, a complete Device Lifecycle Management (DLM) / Asset Management data model for industrial/IoT devices, serves as the single source of truth, providing a stronger and more auditable foundation for data consistency, traceability, operational governance, and cybersecurity review. Using the database as the operational declaration layer gives stronger governance than hand-built runtime diagrams.\n- **data-driven**: seed data and database rows determine what the gateway does;\n- **AI-friendly**: AI agents can inspect, validate, generate, and load structured device data instead of trying to reason over arbitrary visual diagrams;\n- **integration-rich**: Apache Camel provides a large ecosystem of components and Enterprise Integration Patterns (EIP), so the same model can drive MQTT, OPC UA, file transfer, database persistence, and future protocols;\n- **cloud/edge ready**: Quarkus provides fast startup, Java 21 virtual-thread support, container deployment, and optional GraalVM native compilation.\n\nThis is the key differentiation: the gateway does not ask developers to draw fragile runtime flows by hand. It lets engineers and AI agents work against a validated industrial data model, then uses Camel to execute that model safely at the edge.\n\nThe Moqui database is the source of truth. Camel routes are runtime projections of the model.\n\n---\n\n## Model-first before data lake\n\nA `moqui-device` gateway does not start from the idea that industrial data should first be collected into an unstructured data lake and interpreted later. It starts from the device model.\nDevices, PLCs, parameters, recipes, requests, gateway ownership, telemetry, and route activation are linked to explicit model entities from the beginning.\n\nA data lake can still be useful as a downstream analytical or long-term storage layer for raw signals, images, logs, and AI training datasets. However, it is no longer the semantic core of the architecture. The semantic source of truth is the `moqui-device` model; the lake, if present, is a derived storage target.\n\n---\n\n## Why this is different from diagram-first gateways\n\nMany industrial gateway and low-code tools start from visual flows. The developer connects nodes, wires protocol blocks, adds transformations, and progressively builds a runtime diagram. This can be useful for quick prototypes, but the diagram often becomes the source of truth.\n\n`moqui-device-gateway` follows a different approach. The source of truth is not a diagram and not an unstructured data lake. The source of truth is the `moqui-device` model stored in the Moqui database.\n\n| Aspect                  | Diagram-first gateway                                                  | Data-lake-first approach                              | `moqui-device-gateway`                                                                                                    |\n| ----------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |\n| Primary source of truth | Visual flows manually created by developers                            | Raw data collected first, interpreted later           | Structured industrial device model                                                                                        |\n| Device knowledge        | Spread across diagrams, node settings, scripts, and naming conventions | Often reconstructed after ingestion                   | Explicit `Device`, `PhysicalDevice`, `DeviceGroup`, `ParameterDef`, `DeviceRequest`, `DeviceConfig`, and related entities |\n| Gateway behavior        | Encoded in flow diagrams                                               | Usually outside the lake, in separate ingestion jobs  | Declared as data and executed as Camel routes                                                                             |\n| Semantics               | Implicit in the diagram                                                | Often missing or added later                          | Present before data collection starts                                                                                     |\n| AI assistance           | Must interpret visual flows and ad hoc configuration                   | Must infer meaning from raw tables, topics, and files | Can inspect and generate structured model data                                                                            |\n| Change control          | Flow diff and review can be difficult                                  | Raw data does not describe operational intent         | Database records and seed XML are reviewable, versionable, and auditable                                                  |\n| Runtime execution       | Tool-specific flow runtime                                             | External pipelines and jobs                           | Apache Camel routes with Enterprise Integration Patterns                                                                  |\n| Long-term analytics     | Usually added separately                                               | Central architectural focus                           | Optional downstream sink fed by a model-aware system                                                                      |\n\nThe practical consequence is important: the gateway does not merely move data from PLCs to a database or broker. It executes a modeled industrial system. A telemetry value, a recipe parameter, a PLC request, or a subscription route is not an anonymous data point; it is connected to a device, a parameter definition, a configuration, a request, and a gateway responsibility.\n\nThis is what makes the component model-driven and AI-friendly. AI agents do not have to reverse-engineer the meaning of arbitrary diagrams or unstructured lake records. They can work against explicit model entities, validate completeness, generate seed data, review route intent, and help activate Camel routes from a controlled operational model.\n\n---\n\n## AI-ready engineering workflow\n\nBecause the gateway is driven by model data, AI agents can participate safely in the engineering workflow without becoming the source of truth. The agent does not need to reverse-engineer a visual flow or edit opaque runtime wiring. It can operate on explicit entities, relationships, enumerations, seed XML, SQL checks, and tests.\n\nThis makes the gateway suitable for AI-assisted industrial engineering workflows such as:\n\n- reviewing whether a gateway is correctly associated with its PLCs through `DeviceGroup` and `DeviceGroupMember`;\n- checking whether `DeviceRequest` and `DeviceRequestItem` rows are complete before a route is activated;\n- generating or reviewing seed data for devices, parameters, requests, and subscriptions;\n- detecting missing gateway identity, missing PLC membership, invalid request scope, or incomplete transport configuration;\n- producing repeatable activation and test procedures from the model;\n- supporting safer deployment reviews before Camel routes run at the edge.\n\nThe important point is not that AI replaces engineering validation. The point is that a structured industrial model gives AI agents a precise and reviewable control surface. With diagram-first tools, the agent usually has to reason over informal visual wiring. With `moqui-device-gateway`, the agent reasons over database-backed model entities and executable tests.\n\n---\n\n## What this component does\n\n`moqui-device-gateway` runs outside the main Moqui web application, close to PLCs, brokers, OPC UA servers, and OT network resources.\n\nIt can:\n\n- execute `DeviceRequest` rows defined in the `moqui-device` model;\n- publish MQTT messages from `Parameter` values;\n- subscribe to MQTT topics and store inbound values;\n- read, write, or subscribe to OPC UA nodes;\n- ingest PLC diagnostic logs;\n- export device configuration, recipe, and batch-management data;\n- transfer device content when configured (CNC G-Code file, binary code, upgrade, docs, etc.);\n- expose REST endpoints to trigger configured requests;\n- restore startup subscriptions from the Moqui database.\n\nIt does **not**:\n\n- replace Moqui;\n- define the canonical device model by itself;\n- use local JSON files as the subscription source of truth;\n- require hand-built flow diagrams as the primary configuration mechanism;\n- decide which PLC belongs to which gateway by hard-coded Java logic.\n\n---\n\n## Runtime architecture\n\n```text\nMoqui database\n  └─ moqui-device model\n      ├─ Device / PhysicalDevice\n      ├─ DeviceGroup / DeviceGroupMember\n      ├─ DeviceRequest / DeviceRequestItem\n      ├─ ParameterDef / Parameter / ParameterLog\n      ├─ DeviceConfigSet / DeviceConfig (recipe and batch-management definitions)\n      ├─ DeviceRuleSet / DeviceRule (recipe/configuration instantiation and application)\n      └─ DeviceConnection\n            ↓\nmoqui-device-gateway\n  └─ Quarkus + Apache Camel runtime routes\n            ↓\nMQTT / OPC UA / PLC / PostgreSQL / file transfer\n```\n\nThe gateway does not hard-code PLCs, parameters, MQTT topics, OPC UA nodes, or device recipes. It reads model rows and creates runtime behavior from them.\n\nThe main rule is:\n\n```text\nmoqui-device model rows = persistent declaration\nCamel routes = runtime execution\n```\n\n---\n\n## moqui-device model used by the gateway\n\n| Entity | Purpose in the gateway |\n|---|---|\n| `Device` | Logical identity of a gateway, PLC, controller, IIoT controller, remote IO, drive/inverter, soft starter, servodrive or other modeled device. |\n| `PhysicalDevice` | Physical representation of a gateway, PLC, controller, remote IO, drive/inverter, soft starter, servodrive, broker adapter, or other real device. |\n| `DeviceGroup` | Logical group that associates an edge gateway with the PLCs/devices it is responsible for. |\n| `DeviceGroupMember` | Membership and role of each device inside a `DeviceGroup`, including `DgmpEdgeGateway` for the gateway process. |\n| `DeviceRequest` | Declarative request to execute: write, subscribe, unsubscribe, export, transfer, and similar operations. |\n| `DeviceRequestItem` | Parameters, topics, node IDs, or other item-level targets belonging to a request. |\n| `ParameterDef` | Definition of a machine, process, telemetry, command, recipe, or configuration parameter. |\n| `Parameter` | Current value or state of a parameter. |\n| `ParameterLog` | Historical/inbound values written by telemetry routes. |\n| `DeviceConfigSet` | Group of device configurations, typically used to represent recipe and batch-management definitions. |\n| `DeviceConfig` | Machine-side configuration or recipe definition: a set of target values, limits, modes, or settings for a device/process. |\n| `DeviceRuleSet` | Rule/configuration set used to instantiate or apply a recipe/configuration to a specific device or production context. |\n| `DeviceRule` | Concrete rule or binding that connects a configured process/device behavior to runtime parameters and actions. |\n| `DeviceConnection` | Optional transport connection details, especially useful for OPC UA and direct device links on fieldbus protocols. |\n\nIn this model, a recipe is not a separate hand-coded gateway flow. A recipe is a machine-side `DeviceConfig`: a structured configuration of parameters and rules that can be stored, reviewed, versioned, exported, and applied through the same model used for devices, telemetry, and requests. This aligns the gateway with established recipe and batch-management concepts, where configuration, execution, and traceability must stay aligned.\n\nThe gateway assumes the real production schema and seed lifecycle are managed by Moqui and the `moqui-device` component. The SQL files under `src/test/resources` are local test fixtures and examples only.\n\n---\n\n## Gateway to PLC association\n\nThe gateway itself is modeled as a `Device` + `PhysicalDevice`.\n\nA PLC is also modeled as a `Device` + `PhysicalDevice`.\n\nThe association between a gateway and the PLCs it serves is modeled through `DeviceGroup` and `DeviceGroupMember`.\n\nExample:\n\n```text\nDeviceGroup:\n  DG_LINE_01_EDGE\n\nDeviceGroupMember:\n  DG_LINE_01_EDGE / GW_EDGE_01  / DgmpEdgeGateway\n  DG_LINE_01_EDGE / PLC_LINE_01 / DgmpProcessPLC\n  DG_LINE_01_EDGE / PLC_LINE_02 / DgmpProcessPLC\n```\n\nThe important enumeration is:\n\n```xml\n\u003cmoqui.basic.Enumeration\n    enumId=\"DgmpEdgeGateway\"\n    description=\"Edge Gateway\"\n    enumTypeId=\"DeviceGroupMemberPurpose\"/\u003e\n```\n\nMeaning:\n\n- `DgmpEdgeGateway` identifies the gateway process responsible for a group;\n- `DgmpProcessPLC`, `DgmpSafetyPLC`, `DgmpController`, `DgmpRemoteIO`, and similar values identify target PLC/controller/remote-IO members;\n- `DeviceRequest.deviceId` remains the target device, usually the PLC;\n- `DeviceRequest.brokerUri` remains the broker or transport endpoint;\n- `DeviceRequest.query` and/or `DeviceRequestItem.query` remain the topic, node, or target address.\n\n`DgmpEdgeGateway` is not the MQTT broker. It is the edge process that executes the request. The broker is still represented by the request transport fields, such as `brokerUri`, or by `DeviceConnection` where appropriate.\n\n---\n\n## Startup subscription discovery\n\nAt startup or after a failure recovery, the gateway discovers active subscriptions from the Moqui database.\n\nStartup discovery follows this chain:\n\n```text\nGATEWAY_DEVICE_ID\n  -\u003e Device + PhysicalDevice validation\n  -\u003e DeviceGroupMember with purposeEnumId = DgmpEdgeGateway\n  -\u003e target PLC/controller/remote-IO members in the same DeviceGroup\n  -\u003e active DeviceRequest rows routed through DrrMoquiDeviceGateway\n  -\u003e dynamic Camel routes\n```\n\nIn practical terms, the gateway starts only from its logical identity, `GATEWAY_DEVICE_ID`. From that identity it finds the PLCs/controllers it is responsible for, then loads the active `DeviceRequest` rows that belong to those target devices. The result is a set of Camel routes created from the model at runtime.\n\nManual REST execution follows the same ownership rule: a gateway can execute only requests belonging to devices inside its own `DeviceGroup` scope.\n\n---\n\n## Runtime configuration\n\nThe most important runtime settings are:\n\n| Variable / property | Required | Description |\n|---|---:|---|\n| `GATEWAY_DEVICE_ID` / `gateway.device.id` | Yes | `PhysicalDevice` ID of this gateway, for example `GW_EDGE_01`. |\n| `QUARKUS_DATASOURCE_JDBC_URL` | Yes | JDBC URL of the Moqui database. |\n| `QUARKUS_DATASOURCE_USERNAME` | Yes | Database user. |\n| `QUARKUS_DATASOURCE_PASSWORD` | Yes | Database password. |\n| `QUARKUS_DATASOURCE_LOG_JDBC_URL` | Optional | Telemetry/log datasource. In small test setups it can point to the same DB. |\n| `GATEWAY_API_AUTH_TOKEN` | Required when API auth is enabled | REST API token. Do not use the default token in production. |\n| `QUARKUS_HTTP_PORT` | Optional | HTTP port, default `8081`. |\n\nExample:\n\n```bash\nexport GATEWAY_DEVICE_ID=GW_EDGE_01\nexport QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://127.0.0.1:5432/moqui\nexport QUARKUS_DATASOURCE_USERNAME=moqui\nexport QUARKUS_DATASOURCE_PASSWORD=moqui\nexport GATEWAY_API_AUTH_TOKEN='replace-with-a-real-secret'\n```\n\n`GATEWAY_DEVICE_ID` must exist as `Device` + `PhysicalDevice` and must belong to at least one `DeviceGroup` as `DgmpEdgeGateway`.\n\n---\n\n## Quick start for developers\n\n### 1. Build\n\n```bash\nmvn clean package\n```\n\n### 2. Run fast tests\n\n```bash\nmvn test\n```\n\nFast tests are designed to run without the full Moqui runtime and without external MQTT/OPC UA infrastructure where possible.\n\n### 3. Start local PostgreSQL\n\n```bash\ndocker compose -f docker/postgres-compose.yml -p moqui-gateway up -d\n```\n\nThe local PostgreSQL Compose file initializes automatically on first startup. It loads:\n\n```text\nsrc/test/resources/db/init.sql\nsrc/test/resources/device-gateway-seed.sql\nsrc/test/resources/device-gateway-opcua-seed.sql\n```\n\nThe seed templates are expanded with the environment variables defined in `docker/postgres-compose.yml`.\n\nIf you want to re-run initialization from scratch:\n\n```bash\ndocker compose -f docker/postgres-compose.yml -p moqui-gateway down -v\ndocker compose -f docker/postgres-compose.yml -p moqui-gateway up -d\n```\n\nWait until PostgreSQL is healthy:\n\n```bash\ndocker ps --filter name=moqui-gateway-database\n```\n\nVerify that the local fixture data exists:\n\n```bash\nPGPASSWORD=moqui psql -h 127.0.0.1 -p 5432 -U moqui -d moqui \\\n  -c \"select device_id from device order by device_id;\"\n```\n\nPassword for the local compose file:\n\n```text\nmoqui\n```\n\nThis creates sample IDs such as:\n\n```text\nGW_EDGE_01\nVIRTUAL_PLC_01\nDG_EDGE_01\nVPL_MQTT_PUBLISH_REQ_01\nVPL_MQTT_SUB_REQ_01\n```\n\n### 4. Start ActiveMQ Artemis for MQTT tests\n\nFor local MQTT tests, use the ActiveMQ Artemis broker provided by the standard Moqui Docker setup.\n\nFrom the repository layout where `moqui-device-gateway` is next to `moqui-framework`, start Artemis with:\n\n```bash\ndocker compose -f ../moqui-framework/docker/activemq-compose.yml -p moqui-gateway up -d\n```\n\nThe local seed examples expect an MQTT broker compatible with:\n\n```text\npaho-mqtt5:?brokerUrl=tcp://localhost:1883\u0026userName=artemis\u0026password=artemis\n```\n\nThis is the standard local developer mode:\n\n- PostgreSQL via `docker/postgres-compose.yml`\n- ActiveMQ Artemis via `../moqui-framework/docker/activemq-compose.yml`\n- `moqui-device-gateway` running directly on the host JVM with Quarkus/Maven\n\nIn this standard mode, `DeviceRequest.brokerUri` should use `tcp://localhost:1883`.\n\nContainer gateway mode is a separate option. If the gateway itself runs with `docker/device-gateway-compose.yml`, then `DeviceRequest.brokerUri` must use a broker host name reachable from inside the gateway container. That host name depends on the service name and network used by the Moqui framework ActiveMQ Compose file, so it should not be guessed or hard-coded blindly.\n\nAny MQTT broker can be used if the corresponding `DeviceRequest.brokerUri` values are updated, but ActiveMQ Artemis is the reference broker for this component.\n\n### 5. Start the gateway without Docker\n\nFor local developer startup, use the `local` profile:\n\n```bash\nGATEWAY_DEVICE_ID=GW_EDGE_01 \\\njava -Dquarkus.profile=local -jar target/quarkus-app/quarkus-run.jar\n```\n\nEquivalent Maven/Quarkus dev-style startup:\n\n```bash\nGATEWAY_DEVICE_ID=GW_EDGE_01 \\\nmvn quarkus:dev -Dquarkus.profile=local\n```\n\nDefault endpoint:\n\n```text\nhttp://localhost:8081\n```\n\nIf you want to start with the default profile instead, use a real token:\n\n```bash\nGATEWAY_DEVICE_ID=GW_EDGE_01 \\\nGATEWAY_API_AUTH_TOKEN='replace-with-a-real-secret' \\\njava -jar target/quarkus-app/quarkus-run.jar\n```\n\n### 6. Check readiness\n\n```bash\ncurl http://localhost:8081/q/health/ready\n```\n\nReadiness should be `UP` only when the process, database, Camel context, and gateway model identity are valid.\n\n### 7. Trigger a request\n\n```bash\ncurl -X POST \\\n  http://localhost:8081/api/device-request/run/VPL_MQTT_PUBLISH_REQ_01 \\\n  -H 'X-API-Key: change-me'\n```\n\n---\n\n## Running scenarios\n\n### Scenario A — MQTT write / publish\n\nUse this when a `DeviceRequest` represents a write/publish operation.\n\nPrerequisites:\n\n- the gateway exists as `Device` + `PhysicalDevice`;\n- the target PLC exists as `Device` + `PhysicalDevice`;\n- both are in the same `DeviceGroup`;\n- the gateway member has `purposeEnumId = DgmpEdgeGateway`;\n- the PLC member has a PLC/controller purpose such as `DgmpProcessPLC`;\n- a `DeviceRequest` exists with `requestTypeEnumId = DrtWrite`;\n- request items exist in `DeviceRequestItem`;\n- current values exist in `Parameter`.\n\nThese examples use `mosquitto_sub` only as an MQTT client CLI tool. The broker used by the standard local setup is ActiveMQ Artemis.\n\nWatch an outbound MQTT topic:\n\n```bash\nmosquitto_sub -h 127.0.0.1 -p 1883 \\\n  -u artemis -P artemis \\\n  -t 'mqtt-write-device-request/#'\n```\n\nRun the request:\n\n```bash\ncurl -X POST \\\n  http://localhost:8081/api/device-request/run/VPL_MQTT_PUBLISH_REQ_01 \\\n  -H 'X-API-Key: change-me'\n```\n\n### Scenario B — MQTT subscription / inbound parameter update\n\nFor persistent startup subscriptions, do not edit a local JSON file.\n\nCreate or seed an active `DeviceRequest` in the Moqui database and associate the target PLC with the gateway through `DeviceGroupMember`.\n\nRequired model rows:\n\n```text\nDevice + PhysicalDevice: gateway\nDevice + PhysicalDevice: PLC\nDeviceGroup: gateway/PLC scope\nDeviceGroupMember: gateway with DgmpEdgeGateway\nDeviceGroupMember: PLC with DgmpProcessPLC or similar\nDeviceRequest: subscription request routed through DrrMoquiDeviceGateway\nDeviceRequestItem: subscription items/topics/parameters where required\n```\n\nStart the gateway. It will restore eligible subscription routes from the database.\n\nThese examples use `mosquitto_pub` only as an MQTT client CLI tool. The broker used by the standard local setup is ActiveMQ Artemis.\n\nPublish a test inbound message:\n\n```bash\nmosquitto_pub -h 127.0.0.1 -p 1883 \\\n  -u artemis -P artemis \\\n  -t 'mqtt-subscribe-device-request/virtual-plc/feedback' \\\n  -m '{\"parameterId\":\"VPL_PARAM_FEEDBACK_01\",\"numericValue\":12.3,\"purposeEnumId\":\"DrpLogging\"}'\n```\n\nCheck the database:\n\n```sql\nSELECT * FROM PARAMETER WHERE PARAMETER_ID = 'VPL_PARAM_FEEDBACK_01';\nSELECT * FROM PARAMETER_LOG ORDER BY OBSERVED_DATE DESC;\n```\n\n### Scenario C — OPC UA\n\nUse this when `DeviceRequest` / `DeviceRequestItem` rows reference OPC UA node IDs or `DeviceConnection` rows.\n\nTypical setup:\n\n1. create or seed the gateway and PLC devices;\n2. associate them through `DeviceGroup` / `DeviceGroupMember`;\n3. create a `DeviceConnection` for the OPC UA endpoint;\n4. create `DeviceRequest` and `DeviceRequestItem` rows with OPC UA node IDs;\n5. start the OPC UA server or test server;\n6. start the gateway;\n7. trigger the request manually through the REST API. If you want OPC UA subscriptions to be restored at startup, model them with `routerEnumId = DrrMoquiDeviceGateway` and with a request type supported by startup discovery;\n8. verify `Parameter` / `ParameterLog`.\n\nThe sample OPC UA seed is:\n\n```text\nsrc/test/resources/device-gateway-opcua-seed.sql\n```\n\nIt also contains placeholders and must be adapted to the local endpoint and node IDs before use.\n\n### Scenario D — PLC log ingestion\n\nPLC log ingestion routes inbound diagnostic records into the database.\n\nTypical flow:\n\n```text\nPLC/logger -\u003e MQTT/broker/topic -\u003e Camel route -\u003e DEVICE_LOG or related log table\n```\n\nUse this when PLC runtime logs should be persisted separately from normal parameter telemetry.\n\nCheck the configured PLC log topic and payload format in the corresponding `DeviceRequest` and route configuration before testing.\n\n### Scenario E — Device configuration / recipe export\n\nConfiguration export is used when Moqui stores a device configuration, recipe, or batch-management definition and the gateway must export it for an external PLC/device workflow. In this model, a recipe is a structured machine-side `DeviceConfig`, not a separate visual flow.\n\nExample REST call:\n\n```bash\ncurl -X POST \\\n  http://localhost:8081/api/device-config/export \\\n  -H 'Content-Type: application/json' \\\n  -H 'X-API-Key: change-me' \\\n  -d '{\n    \"deviceRuleSetId\":\"VPL_RULESET_1_01\",\n    \"deviceId\":\"VIRTUAL_PLC_01\",\n    \"deviceRuleId\":null\n  }'\n```\n\n### Scenario F — Device content transfer\n\nUse content transfer for file-like payloads such as recipes, device files, or other content that must be sent to a configured destination.\n\nExample:\n\n```bash\ncurl -X POST \\\n  http://localhost:8081/api/device-content/transfer/SFTP_TRANSFER_REQ \\\n  -H 'Content-Type: application/json' \\\n  -H 'X-API-Key: change-me' \\\n  -d '{\n    \"filename\":\"recipe.txt\",\n    \"contentBase64\":\"SGVsbG8K\"\n  }'\n```\n\n---\n\n## REST API\n\nAll REST endpoints are under:\n\n```text\n/api\n```\n\nWhen authentication is enabled, pass the API token with either:\n\n```text\nX-API-Key: \u003ctoken\u003e\n```\n\nor:\n\n```text\nAuthorization: Bearer \u003ctoken\u003e\n```\n\n| Endpoint | Purpose |\n|---|---|\n| `POST /api/device-request/run/{requestName}` | Execute a `DeviceRequest` already defined in the database, including unsubscribe requests modeled as `DrtUnsubscribe`. |\n| `POST /api/device-config/export` | Export device configuration / recipe data for a device workflow. |\n| `POST /api/device-content/transfer/{requestName}` | Transfer configured file-like device content. |\n\n### Run a DeviceRequest\n\n```bash\ncurl -X POST \\\n  http://localhost:8081/api/device-request/run/{requestName} \\\n  -H 'X-API-Key: change-me'\n```\n\nThis endpoint executes a `DeviceRequest` already defined in the database. It does not define the request.\n\nThe gateway enforces scope. A request can run only if its `DEVICE_ID` belongs to a `DeviceGroup` served by the configured `GATEWAY_DEVICE_ID`.\n\n### Unsubscribe\n\nUnsubscribe is not exposed through a separate REST path in the current gateway implementation.\n\nTo stop a runtime subscription route, create or use a `DeviceRequest` whose `REQUEST_TYPE_ENUM_ID` is `DrtUnsubscribe`, then execute it with:\n\n```bash\ncurl -X POST \\\n  http://localhost:8081/api/device-request/run/{unsubscribeRequestName} \\\n  -H 'X-API-Key: change-me'\n```\n\nAt runtime, the `run/{requestName}` endpoint dispatches that request to the unsubscribe flow.\n\nIt does not permanently deactivate the `DeviceRequest`.\n\nTo prevent a startup subscription from being restored after restart, update the persistent model in the Moqui database. Common options are:\n\n- set `DeviceRequest.thruDate`;\n- change `DeviceRequest.routerEnumId`;\n- remove or disable the relevant `DeviceGroupMember` association.\n\nFor request bodies and longer examples, see the running scenarios above.\n\n---\n\n## Testing\n\n### Fast/local tests\n\n```bash\nmvn test\n```\n\nThese tests should not require a complete Moqui runtime.\n\n### Integration tests\n\nIntegration tests require external services such as PostgreSQL, MQTT broker, and OPC UA test infrastructure.\n\nThe standard local MQTT broker for integration tests is ActiveMQ Artemis from:\n\n```bash\ndocker compose -f ../moqui-framework/docker/activemq-compose.yml -p moqui-gateway up -d\n```\n\nRun examples:\n\n```bash\nmvn -Dquarkus.profile=integration -Dtest=MqttInboundIntegrationTest test\nmvn -Dquarkus.profile=integration -Dtest=GatewaySeededRouteIntegrationTest test\nmvn -Dquarkus.profile=integration -Dtest=OpcUaGatewayIntegrationTest test\nmvn -Dquarkus.profile=integration -Dtest=PlcLogIngestIntegrationTest test\n```\n\nOr use:\n\n```bash\n./scripts/run-integration-tests.sh\n```\n\nBefore considering the component production-ready for a plant, run at least:\n\n- startup restore from DB;\n- MQTT write;\n- MQTT subscribe;\n- gateway crash/restart;\n- broker down/up;\n- OPC UA read/write/subscribe if used;\n- PLC log ingestion if used;\n- readiness behavior with broker/PLC temporarily unreachable.\n\n### Important note about test SQL\n\n`src/test/resources/db/init.sql` is a minimal PostgreSQL schema used by automated tests and local examples.\n\nIt is **not** the production schema and it is **not** the canonical `moqui-device` data model.\n\nIn production, tables and seed data are managed by Moqui and by the `moqui-device` component.\n\n### Seed examples\n\n`src/test/resources/device-gateway-seed.sql` contains local MQTT-oriented test data.\n\n`src/test/resources/device-gateway-opcua-seed.sql` contains local OPC UA-oriented test data.\n\nThey are useful examples, but production seed data should live in Moqui component data files or be inserted through Moqui services/screens.\n\n---\n\n## Docker Compose\n\nThis repository includes simple local Compose files:\n\n```text\ndocker/postgres-compose.yml\ndocker/device-gateway-compose.yml\n```\n\nThe broker used by the standard local setup is still ActiveMQ Artemis from the Moqui framework Docker directory:\n\n```bash\ndocker compose -f ../moqui-framework/docker/activemq-compose.yml -p moqui-gateway up -d\n```\n\nStart PostgreSQL:\n\n```bash\ndocker compose -f docker/postgres-compose.yml -p moqui-gateway up -d\n```\n\nThe PostgreSQL Compose file auto-loads the local test schema and seed on first startup. If the named volume already exists, initialization is not repeated.\n\nBuild and start the gateway:\n\n```bash\ndocker compose -f docker/device-gateway-compose.yml -p moqui-gateway up -d --build\n```\n\nTo build the gateway container with the native Dockerfile:\n\n```bash\nDEVICE_GATEWAY_DOCKERFILE=src/main/docker/Dockerfile.graalvm \\\nDEVICE_GATEWAY_IMAGE=moqui-device-gateway:native \\\ndocker compose -f docker/device-gateway-compose.yml -p moqui-gateway up -d --build\n```\n\nCheck readiness:\n\n```bash\ncurl http://localhost:8081/q/health/ready\n```\n\nStop services:\n\n```bash\ndocker compose -f docker/device-gateway-compose.yml -p moqui-gateway down\ndocker compose -f docker/postgres-compose.yml -p moqui-gateway down\n```\n\nRemove volumes too:\n\n```bash\ndocker compose -f docker/device-gateway-compose.yml -p moqui-gateway down -v\ndocker compose -f docker/postgres-compose.yml -p moqui-gateway down -v\n```\n\nThe local Compose files are for development and simple testing. Production deployments should use real Moqui database infrastructure, secrets, backups, network policy, and monitoring.\n\n---\n\n## Docker Swarm deployment\n\nFor Swarm, configure the logical gateway identity explicitly:\n\n```yaml\nenvironment:\n  - GATEWAY_DEVICE_ID=GW_EDGE_01\n```\n\nRules:\n\n- use one active replica per logical `GATEWAY_DEVICE_ID`;\n- multiple simultaneous gateways should be modeled as different `PhysicalDevice` gateway records;\n- assign each gateway to its own `DeviceGroup` scope or to clearly controlled shared scopes;\n- do not run two active containers with the same `GATEWAY_DEVICE_ID` unless you add an external leader-election or lease mechanism;\n- Swarm failover means the same logical gateway is restarted on another node;\n- local `/deployments/data` is not the subscription source of truth;\n- startup subscriptions are restored from the Moqui database.\n\nExample logical layout:\n\n```text\nGW_EDGE_01 -\u003e Line 1 PLCs\nGW_EDGE_02 -\u003e Line 2 PLCs\nGW_EDGE_03 -\u003e Drying/Maturation cells\n```\n\nIf this component is checked out inside a larger deployment repository, that repository may provide production-oriented Swarm files with Docker secrets, configs, shared volumes, restart policy, and update policy.\n\n---\n\n## MQTT persistent subscriptions\n\nThe gateway can restore subscription routes from the database after restart.\n\nThis is not the same thing as MQTT broker-side persistent session delivery.\n\n### DB-driven startup restore\n\nDB-driven startup restore means:\n\n```text\nDeviceRequest in DB -\u003e gateway startup discovery -\u003e Camel subscription route\n```\n\nIt recreates the route after gateway restart.\n\n### MQTT broker-side persistent session\n\nBroker-side persistent MQTT delivery determines whether messages published while the gateway was offline can be delivered after reconnect.\n\nFor broker-side persistent subscriptions, all of these must be true:\n\n- stable MQTT `clientId`;\n- `cleanStart=false`;\n- `sessionExpiryInterval \u003e 0`;\n- QoS suitable for the required delivery semantics;\n- broker persistence enabled/configured.\n\nWhen the MQTT URI does not already define a `clientId`, the gateway generates a stable one based on:\n\n```text\nGATEWAY_DEVICE_ID + requestName + item index\n```\n\nExample:\n\n```text\nmoqui-gw-GW_EDGE_01-VPL_MQTT_SUB_REQ_01-0\n```\n\nDo not use container ID or hostname as MQTT `clientId` if the subscription must survive Swarm failover. Those values can change when the container is rescheduled.\n\n---\n\n## Health check\n\nUse:\n\n```text\n/q/health/ready\n```\n\nReadiness checks whether the gateway process can operate as a gateway process.\n\nIt checks:\n\n- process is running;\n- Camel context is started;\n- database is reachable;\n- `GATEWAY_DEVICE_ID` is configured;\n- the gateway exists as `Device` + `PhysicalDevice`;\n- the gateway belongs to at least one `DeviceGroup` as `DgmpEdgeGateway`.\n\nReadiness does **not** mean every PLC, MQTT broker, or OPC UA server is currently reachable.\n\nReadiness should not go `DOWN` only because a remote PLC or MQTT broker is temporarily unavailable. Field-level failures should be handled through MQTT Last Will messages, route error handling, logs, and optional diagnostic records in Moqui.\n\n---\n\n## Security notes\n\nFor production:\n\n- replace `GATEWAY_API_AUTH_TOKEN` with a real secret;\n- do not deploy with `change-me` or `change-me-in-production`;\n- use Docker secrets or an equivalent secret manager;\n- restrict network exposure of port `8081`;\n- expose the gateway only to trusted OT/IT networks;\n- use TLS where required by the plant network policy;\n- monitor logs and failed route starts.\n\n---\n\n## Troubleshooting\n\n| Symptom | Likely cause | What to check |\n|---|---|---|\n| `/q/health/ready` is `DOWN` | Missing or invalid gateway identity | Check `GATEWAY_DEVICE_ID`, `Device`, `PhysicalDevice`, `DeviceGroupMember`. |\n| Gateway starts but restores no routes | Gateway is not linked to PLCs through a valid group | Check `DgmpEdgeGateway`, target PLC membership, `STATUS_ID`, `routerEnumId`, and dates. |\n| Manual request is rejected | Request belongs to a PLC outside this gateway scope | Check `DeviceRequest.deviceId` and `DeviceGroupMember` associations. |\n| Request returns an error or no route starts | Invalid broker URI, topic, OPC UA node, connection, or Camel endpoint | Check `DeviceRequest`, `DeviceRequestItem`, `DeviceConnection`, and logs. |\n| MQTT messages are not received after restart | Broker persistent session is not configured | Check `clientId`, `cleanStart`, `sessionExpiryInterval`, QoS, and broker persistence. |\n| Inbound value is not stored | Payload does not match expected parameter mapping | Check payload, `parameterId`, `Parameter`, and `ParameterLog`. |\n| Duplicate MQTT subscriptions | Two active containers use the same `GATEWAY_DEVICE_ID` | Use one active replica per gateway ID unless leader election is added. |\n| Local Docker gateway is not ready | Test schema or seed not loaded | Load `init.sql`, adapt/load seed data, and verify `GW_EDGE_01` exists. |\n\nUseful SQL checks:\n\n```sql\nSELECT * FROM DEVICE WHERE DEVICE_ID = 'GW_EDGE_01';\nSELECT * FROM PHYSICAL_DEVICE WHERE DEVICE_ID = 'GW_EDGE_01';\nSELECT * FROM DEVICE_GROUP_MEMBER WHERE MEMBER_DEVICE_ID = 'GW_EDGE_01';\nSELECT * FROM DEVICE_REQUEST ORDER BY REQUEST_NAME;\nSELECT * FROM DEVICE_REQUEST_ITEM ORDER BY REQUEST_NAME, SEQUENCE_NUM;\nSELECT * FROM PARAMETER ORDER BY PARAMETER_ID;\nSELECT * FROM PARAMETER_LOG ORDER BY OBSERVED_DATE DESC;\n```\n\n---\n\n## Scope and limitations\n\nThis README describes operational usage, model configuration, local testing, and deployment principles.\n\nIt intentionally does not document every internal Java class or every Camel route implementation detail.\n\nThe important operational rule is:\n\n```text\nThe model defines what the gateway may do.\nThe REST API and startup discovery activate what the model declares.\nCamel routes execute the model at runtime.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoqui%2Fmoqui-device-gateway","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoqui%2Fmoqui-device-gateway","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoqui%2Fmoqui-device-gateway/lists"}