Live demonstration of streaming industrial OT data from Ignition to Databricks Delta Lake via Zerobus, with Swinging Door Trending (SDT) compression at the connector layer.
graph TD
subgraph "Edge / Ignition Gateway"
IG[Ignition Gateway<br/>OPC-UA Tag Provider]
EX[Exception Filter<br/>Layer 1: deadband]
end
subgraph "Connector"
ZC[Zerobus Connector<br/>+ SDT Compression Engine<br/>Layer 2: swinging door]
end
subgraph "Databricks Lakehouse"
ZI[Zerobus Ingest<br/>Serverless]
BZ[Bronze: raw_tags<br/>Layer 3: Delta columnar]
SV[Silver: aggregated_tags]
MT[Metrics: ingest_metrics]
end
subgraph "Demo App"
BE[Express API Server<br/>port 3001]
FE[React Frontend<br/>port 5173]
end
IG --> EX --> ZC
ZC -->|gRPC / Protobuf| ZI
ZI --> BZ
BZ --> SV
BZ --> MT
BZ --> BE
SV --> BE
MT --> BE
BE --> FE
- Node.js 20+
- A Databricks workspace with Unity Catalog and a SQL Warehouse
- Zerobus Ingest endpoint (Public Preview)
For the full Ignition 8.3 gateway (Docker), Databricks bundle deploy, and synthetic tag traffic from the repo root, use Make. Step-by-step commands, reruns, and resets are in docs/make-workflow.md.
Quick path:
make bootstrap-83
make setup-wizard-83 configure-83 simulate-83 links-83Run make help from the repo root for all targets. Deeper tables and troubleshooting: CLAUDE.md.
-
Clone the repository and checkout the
agl-demobranch:git clone <repo-url> git checkout agl-demo
-
Copy
.env.exampleto.envand fill in your credentials:cp demo/.env.example demo/.env
-
Install dependencies for each package:
npm --prefix demo/frontend install npm --prefix demo/simulator install
-
Create the Databricks tables by running
pipelines/sql/setup_tables.sqlagainst your SQL Warehouse. Replace${catalog}and${schema}with your values (defaults:agl_demo.ot). -
Start the frontend:
npm --prefix demo/frontend run dev
-
Start the simulator:
npm --prefix demo/simulator run dev
| Variable | Required | Default | Description |
|---|---|---|---|
DATABRICKS_HOST |
Yes | - | Workspace URL |
DATABRICKS_TOKEN |
Auth* | - | PAT or OAuth token (local dev) |
DATABRICKS_CLIENT_ID |
Auth* | - | Service principal client ID (Databricks Apps) |
DATABRICKS_CLIENT_SECRET |
Auth* | - | Service principal client secret (Databricks Apps) |
DATABRICKS_HTTP_PATH |
Yes | - | SQL warehouse HTTP path |
DATABRICKS_WAREHOUSE_ID |
No | - | SQL warehouse ID (injected by app.yaml) |
DATABRICKS_CATALOG |
No | agl_demo |
Unity Catalog name |
DATABRICKS_SCHEMA |
No | ot |
Schema name |
*Auth: provide either DATABRICKS_TOKEN or both DATABRICKS_CLIENT_ID + DATABRICKS_CLIENT_SECRET.
| ZEROBUS_ENDPOINT | No | - | Zerobus gRPC endpoint |
| ZEROBUS_CLIENT_ID | No | - | OAuth client ID |
| ZEROBUS_CLIENT_SECRET | No | - | OAuth client secret |
| ZEROBUS_TABLE | No | - | Target Delta table FQN |
| SDT_DEFAULT_COMP_DEV_PERCENT | No | 1.0 | Default SDT compression deviation % |
| SDT_DEFAULT_COMP_MAX_SECONDS | No | 600 | Default max archive interval (seconds) |
demo/ Databricks demo application
frontend/ React 18 + Vite + Tailwind CSS dashboard
backend/ Express API server with Databricks SQL connector
simulator/ Ignition tag simulator + Zerobus publisher
app/ Databricks Apps deployment config (app.yaml)
pipelines/ Databricks data processing
sql/ SQL scripts for table setup and transforms (Bronze → Silver → Gold)
sites/ Per-customer site SQL packs
module/ Java Ignition Gateway module (separate build)
examples/ Ignition site configurations
docker/ Docker build and gateway configs
cd demo && npm run demo:startThis single command verifies .env is configured, then starts the backend, frontend, and simulator concurrently.
This is the complete local deployment flow for running the demo from scratch. It sets up an Ignition Gateway in Docker, auto-configures the Zerobus connector, and streams simulated industrial data to Databricks.
- Docker — via Colima (macOS, recommended:
colima start --memory 10 --cpu 4) or Docker Desktop - uv — Python package manager (install)
- A Databricks workspace with Unity Catalog, a SQL Warehouse, and Zerobus Ingest enabled
- A Databricks service principal with OAuth M2M credentials (Client ID + Client Secret)
- A
~/.databrickscfgprofile configured for the service principal:
[my-demo]
host = https://adb-<workspace-id>.<suffix>.azuredatabricks.net
client_id = <service-principal-client-id>
client_secret = <service-principal-client-secret>
auth_type = oauth-m2mRun the setup SQL against your workspace (update catalog/schema/SP ID as needed):
# Using the Databricks CLI
databricks sql execute --profile my-demo \
--statement "CREATE CATALOG IF NOT EXISTS my_catalog"
# Or run the full setup script
# See examples/agl_fleet/setup_databricks.sql for the complete set of
# CREATE/GRANT statements needed for the service principalThe service principal needs USE CATALOG, USE SCHEMA, MODIFY, and SELECT on the target table. See examples/agl_fleet/setup_databricks.sql for a working example.
cd docker/ignition-gateway
docker compose -f docker-compose.83.yml up -dWait for the container to be healthy (~60s on first boot):
docker ps --filter name=ignition83_7088 --format '{{.Status}}'
# Should show: Up ... (healthy)On first boot, complete the setup wizard at http://localhost:7088:
- Accept the EULA
- Create an admin user (e.g.
admin/password) - Skip edition selection (trial is fine)
- Finish setup
The Zerobus module is auto-loaded from the bind-mounted .modl file.
cd examples/agl_fleet
# One command: reads credentials from your Databricks CLI profile
# and pushes the full connection config to the running Gateway
uv run --extra setup agl-sim --setup-only \
--profile my-demo \
--zerobus-endpoint <workspace-id>.zerobus.<region>.<cloud-domain>This uses a GET-then-merge approach, so it won't overwrite other settings. You can also configure manually via curl — see DEPLOYMENT.md for the full REST API reference.
Verify the connection:
# Health check — should return {"status":"ok","enabled":true}
curl -s http://localhost:7088/system/zerobus/health
# Full diagnostics — look for Connected: true
curl -s http://localhost:7088/system/zerobus/diagnosticscd examples/agl_fleet
uv sync
uv run agl-sim --gateway http://localhost:7088You should see events being accepted with dropped=0:
[tick 1] sent=48 accepted=48 dropped=0
[tick 2] sent=39 accepted=39 dropped=0
SELECT COUNT(*) AS row_count,
MIN(ingestion_timestamp) AS earliest,
MAX(ingestion_timestamp) AS latest
FROM <catalog>.<schema>.<table>
WHERE ingestion_timestamp >= current_timestamp() - INTERVAL 10 MINUTES;# Stop the simulator
Ctrl+C
# Stop the Gateway (preserves state for next time)
cd docker/ignition-gateway && docker compose -f docker-compose.83.yml down
# Full reset (removes volumes, requires setup wizard again)
cd docker/ignition-gateway && docker compose -f docker-compose.83.yml down -v| Topic | Document |
|---|---|
| Production deployment & configuration | DEPLOYMENT.md |
| Docker/Colima gateway setup | docker/ignition-gateway/README.md |
| AGL Fleet Simulator & auto-config | examples/agl_fleet/README.md |
| Databricks table setup & SP grants | examples/agl_fleet/setup_databricks.sql |
| Module build from source | CLAUDE.md (Docker-based build section) |
The Swinging Door Trending (SDT) algorithm compresses time-series data at the connector layer, matching common historian compression semantics. It works by maintaining a "door" around the last archived point - only when a new value deviates beyond the configurable CompDev threshold does it trigger an archive event.
Two-layer compression:
- SDT at the connector - Swinging Door Trending filters out redundant values before they reach Databricks. Configurable per tag pattern via
comp_dev_percent(% of tag span) andcomp_max_seconds(max archive interval). - Delta columnar encoding - Parquet dictionary, RLE, bit-packing, and Zstd compression on top of the SDT-filtered data.
Key parameters:
| Parameter | Description | Default |
|---|---|---|
comp_dev_percent |
Compression deviation as % of tag span | 1.0% |
comp_max_seconds |
Max time between forced archive events | 600s |
comp_min_seconds |
Min time between archive events | 0s |
Typical compression ratios range from 4:1 to 10:1 depending on signal characteristics.
npm --prefix demo/frontend run test -- --run
npm --prefix demo/simulator run test -- --runcd demo && bash gates.shThis runs install, lint, typecheck, test, and build gates across all packages.
The demo app (backend + frontend) can be deployed as a Databricks App using git integration. The simulator runs separately (on-prem or locally).
- A Databricks workspace with Apps enabled
- A SQL warehouse (serverless recommended)
- The
agl-demobranch pushed to a git repo accessible from Databricks
-
Clone the repo into your workspace as a Git Folder:
- In Databricks, go to Workspace > Repos > Add Repo
- Enter the repo URL and select the
agl-demobranch
-
Create the app in your Databricks workspace:
databricks apps create agl-ot-demo --description "AGL OT Lakehouse Demo" -
Add resources - in the Databricks Apps UI, click "Configure" on the app and add a SQL warehouse resource with key
sql-warehouse. Grant the app's service principalCAN USEpermission on the warehouse. -
Deploy from workspace - point the app at the
demo/app/directory within the cloned repo:databricks apps deploy agl-ot-demo \ --source-code-path /Workspace/Repos/<your-user>/lakeflow-ignition-zerobus-connector/demo/app
-
Verify - open the app URL shown in the deployment output. The dashboard should load and display live metrics once the simulator is sending data.
-
Stream logs to debug startup issues:
databricks apps logs agl-ot-demo --follow
The demo/app/ directory contains:
app.yaml- Databricks Apps configuration (command, env vars, resource references)package.json- Build and start scriptsscripts/copy-static.js- Copies frontend build output for Express to serve
When deployed, Databricks Apps:
- Runs
npm run buildwhich installs and builds both frontend and backend, then copies frontend assets todemo/app/static/ - Runs
npm run start(fromapp.yamlcommand) which starts Express serving both the API and static frontend on$PORT
The app's service principal automatically receives DATABRICKS_CLIENT_ID, DATABRICKS_CLIENT_SECRET, and DATABRICKS_HOST as environment variables. The SQL warehouse ID is injected via the sql-warehouse resource reference in app.yaml.
- Port binding errors: The app MUST listen on
process.env.PORT. Do not hardcode a port. - Missing resources: If
DATABRICKS_WAREHOUSE_IDis empty, ensure the SQL warehouse resource is attached to the app. - Auth failures: The app uses service principal OAuth (M2M). Verify the service principal has access to the catalog and warehouse.
- Build failures: Run
cd demo/app && npm run buildlocally to debug build issues before deploying. - Frontend not loading: Check that
demo/app/static/is populated after build. The Express server serves these files in production mode (NODE_ENV=production).
Version: 1.0.1
Purpose: Stream Ignition tag-change events to Databricks Delta tables via Zerobus (gRPC + protobuf).
Ignition compatibility: 8.1.x and 8.3.x (different .modl artifacts).
Configuration: via the Ignition Gateway UI.
An Ignition Gateway is the runtime server for the Ignition platform. It connects to industrial data sources (OPC UA, MQTT, PLC drivers, etc.), exposes those values as tags, and runs gateway services (history, alarming, scripting, eventing). This module runs inside the Gateway as an Ignition module (.modl).
Databricks Zerobus is Databricks' managed real-time ingestion transport used to stream events into Delta (typically landing in a Bronze table).
Using Zerobus means you get a streaming ingestion path without operating Kafka infrastructure:
- no standing up brokers/zookeepers/controllers
- no partition planning / retention management
- no connector fleet management
Instead, this module batches tag-change events and streams them directly to Databricks over gRPC/protobuf.
This connector is agnostic to the underlying OT/IIoT source because it subscribes to Ignition tags, not to a specific protocol.
- Ignition normalizes sources into tags: Whether values originate from OPC UA, MQTT, PLC drivers, historians, or other tag providers, Ignition exposes them through the same Tag system and emits the same tag-change callbacks.
- Stable event schema: The module converts a tag change into a single protobuf message (see
module/src/main/proto/ot_event.proto). Since the event payload is about the tag observation (tag path, timestamp, value, quality, etc.), you do not change protobuf/schema when you switch protocols - you only change which tag paths you subscribe to.
Only the tag provider (the left-most portion of the tag path) and the tag paths you select.
Examples (illustrative):
- OPC UA tags:
[MyOpcUa]Devices/Turbine1/Speed[MyOpcUa]Devices/Turbine1/Temperature
- MQTT tags (via MQTT Engine / Transmission providers):
[MQTT Engine]Sparkplug B/Group/Edge Node/Device/pressure[MQTT Engine]Sparkplug B/Group/Edge Node/Device/vibration_rms
- Simulated/demo tags:
[Sample_Tags]Sine/Sine0[Sample_Tags]Ramp/Ramp0
In all cases, the connector publishes the same protobuf event type to Databricks and writes into the same Delta table schema.
- Get started (download the module)
- Repository layout
- Release artifacts (two
.modlfiles) - Developer build
- Reference
For production setup (prereqs, install, configure, verify, troubleshooting), see DEPLOYMENT.md.
Download the prebuilt Ignition module (.modl) from GitHub Releases:
- Ignition 8.1.x:
zerobus-connector-1.0.1.modl - Ignition 8.3.x:
zerobus-connector-1.0.1-ignition-8.3.modl
Then follow DEPLOYMENT.md for installation and configuration.
Canonical locations:
- Module source/build:
module/ - Published module artifacts (
.modl):releases/(repo root)
Directory structure (high-level):
.
├── README.md
├── DEPLOYMENT.md
├── releases/ # canonical .modl artifacts (root)
│ ├── zerobus-connector-1.0.1.modl
│ └── zerobus-connector-1.0.1-ignition-8.3.modl
├── module/ # Ignition module source + Gradle build
│ ├── build.gradle
│ ├── settings.gradle
│ ├── gradlew
│ └── src/
│ └── main/
│ ├── java/ # gateway hooks, services, servlet layer
│ ├── resources/ # module.xml, i18n, UI assets (web/, mounted/)
│ └── proto/ # protobuf schema (ot_event.proto)
├── examples/ # end-to-end demo simulations (Ignition tags + timer scripts)
│ ├── tilt_renewables_site01/
│ ├── saint_gobain_site01/
│ └── tilt_sim/
├── tools/ # Databricks SQL packs (Bronze->Silver->Gold) + dashboard/genie prompts
│ ├── databricks_end2end_tilt/
│ └── databricks_end2end_sg/
└── onboarding/
├── databricks/ # optional: helper to create/align target table schema
└── ignition/
├── 8.1.50/README.md
└── 8.3.2/README.md
There are two prebuilt module packages under releases/:
-
releases/zerobus-connector-1.0.1.modl:- Install on: Ignition 8.1.x (and 8.2.x if you run it)
- Why: the packaged
module.xmlsets<requiredIgnitionVersion>to8.1.0
-
releases/zerobus-connector-1.0.1-ignition-8.3.modl:- Install on: Ignition 8.3.x
- Why: the packaged
module.xmlsets<requiredIgnitionVersion>to8.3.0
Ignition enforces compatibility based on module.xml during install. Because 8.3 refuses modules whose requiredIgnitionVersion is below 8.3, we ship two .modl artifacts.
The runtime behavior and code are the same; the important differences are:
module.xmlgate: different<requiredIgnitionVersion>value, produced by the Gradle-PminIgnitionVersion=...build flag.- Servlet API at runtime:
- Ignition 8.1 uses
javax.servlet - Ignition 8.3 uses
jakarta.servlet - The module includes both servlet implementations and selects the right one at runtime via
module/src/main/java/com/example/ignition/zerobus/web/ZerobusConfigServlet.java.
- Ignition 8.1 uses
- JDK 17 installed (Gradle/tooling).
- Ignition SDK jars available locally (used as
compileOnlydependencies):- 8.1.x install at:
/usr/local/ignition8.1 - 8.3.x install at:
/usr/local/ignition
- 8.1.x install at:
Two ways for events to enter the module:
- Direct subscriptions (recommended): in-JVM tag change callbacks from Ignition's TagManager
- HTTP ingest (ingest-only mode): external producer POSTs JSON to module endpoints
One way for events to leave the module:
- Zerobus ingest over gRPC/protobuf to the Databricks Zerobus endpoint
The runtime data path is built as a small pipeline:
- Mapper:
TagEvent -> OTEvent(module/src/main/java/com/example/ignition/zerobus/pipeline/OtEventMapper.java) - Buffer: commit-based buffer backed by memory or disk (
module/src/main/java/com/example/ignition/zerobus/pipeline/StoreAndForwardBuffer.java) - Sink: Zerobus write boundary (
module/src/main/java/com/example/ignition/zerobus/pipeline/EventSink.java)- Zerobus implementation:
ZerobusEventSink->ZerobusClientManager.sendOtEvents(...)
- Zerobus implementation:
Startup
- Gateway hook entrypoints:
- Ignition 8.1.x:
com.example.ignition.zerobus.ZerobusGatewayHook - Ignition 8.3.x:
com.example.ignition.zerobus.ZerobusGatewayHook83
- Ignition 8.1.x:
- PersistentRecord schema is registered (tables created if missing).
- Configuration is loaded from the Gateway internal DB into
com.example.ignition.zerobus.ConfigModel. - Services start only if configuration is valid enough to run (and module is enabled). Invalid config does not fault the module; it keeps services stopped and exposes the error in diagnostics.
Save/apply configuration
- New values are persisted to PersistentRecord.
- Runtime
ConfigModelis updated (updateFrom(...)). - Services are restarted only if necessary (and without crashing the module on validation errors).
- OAuth client secret is stored in the Gateway internal DB (masked in UI); leaving it blank preserves the existing value.
- Tag change happens: Ignition calls into the module via TagManager subscription callbacks.
- Normalize:
TagSubscriptionServicebuilds aTagEvent, then maps it toOTEventviaOtEventMapper. - Buffer: the
OTEventis offered to the buffer (memory or disk store-and-forward). - Flush loop: a scheduled flusher runs every
batchFlushIntervalMs. When the sink is ready, it drains up tobatchSizeevents and calls the sink. - Commit semantics: the buffer is committed only after a successful send (at-least-once).
Prerequisite: set Enable Direct Subscriptions = OFF in the module UI.
- Producer POSTs JSON:
POST /system/zerobus/ingest(single)POST /system/zerobus/ingest/batch(batch)
- Servlet routes the request:
.../web/ZerobusConfigServlet(dispatcher).../web/ZerobusServletHandler(shared request parsing/routing)
- Normalize + buffer: payload ->
TagEvent->OTEvent-> buffer. - Batch/flush/send: same flush loop and sink as direct subscriptions.
When Store-and-Forward is enabled:
- Events are buffered to disk (
DiskSpool) and only removed after successful send (commit). - The module applies high/low watermark backpressure:
- When spool backlog exceeds high watermark, direct subscriptions auto-pause (unsubscribe) and new events may be rejected (instead of unbounded growth).
- When backlog drops below low watermark, direct subscriptions auto-resume.
When the sink is down (auth/network outages):
- Ingestion continues (events keep buffering).
- The flusher does not drain disk unless the sink is ready (prevents repeatedly reading/parsing the same records while disconnected).
- Once auth/network recovers, the sink reconnects, the backlog drains, and subscriptions resume.
cd module
JAVA_HOME=/opt/homebrew/opt/openjdk@17 PATH=/opt/homebrew/opt/openjdk@17/bin:$PATH \
./gradlew buildModule81Output: module/build-user-8.1/modules/zerobus-connector-1.0.1.modl
cd module
JAVA_HOME=/opt/homebrew/opt/openjdk@17 PATH=/opt/homebrew/opt/openjdk@17/bin:$PATH \
./gradlew buildModule83Output: module/build-user-8.3/modules/zerobus-connector-1.0.1-ignition-8.3.modl
After building, the Gradle task also copies the .modl into the repo-level releases/ directory.
By default, the module ID is:
com.example.ignition.zerobus
To build a .modl with a different module ID (for example, to publish under a Module Showcase namespace),
pass -PmoduleId=...:
cd module
./gradlew buildModule83 -PmoduleId=com.databricks.ignition.zerobusNote: Changing module ID means Ignition treats it as a different module (no in-place upgrade/migration).
Ignition modules should be signed for distribution. This repo supports signing as an optional build step, without hardcoding any secrets.
You will need Inductive Automation's module-signer.jar (see IA SDK docs on module signing) and a keystore.
Provide signing config via Gradle properties or environment variables:
MODULE_SIGNER_JAR(or-PmoduleSignerJar=...)SIGNING_KEYSTORE(or-PsigningKeystore=...)SIGNING_STOREPASS(or-PsigningKeystorePassword=...)SIGNING_ALIAS(or-PsigningAlias=...)SIGNING_KEYPASS(or-PsigningAliasPassword=...)- Optional:
SIGNING_CHAIN(or-PsigningChain=...) for CA-signed cert chains
Then run:
cd module
./gradlew signModule83
./gradlew signModule81The signer outputs a new file next to the unsigned module with a -signed.modl suffix.
Gradle needs to know which local Ignition SDK jars to compile against. Run tests like:
Ignition 8.1
cd module
./gradlew test -PignitionHome=/usr/local/ignition8.1 -PbuildForIgnitionVersion=8.1.50Ignition 8.3
cd module
./gradlew test -PignitionHome=/usr/local/ignition -PbuildForIgnitionVersion=8.3.2If you don't want to install Ignition locally just to access SDK jars, you can build the .modl in Docker using:
- the official
inductiveautomation/ignitionimage (source of Ignition SDK/runtime jars) - an official Java 17 JDK image (
eclipse-temurin:17-jdk) for the Gradle build
If you want to run a full Ignition Gateway in Docker (for demos, without installing Ignition on your laptop),
see docker/ignition-gateway/README.md (Colima on macOS).
This uses BuildKit --output to write the .modl to a local folder:
Ignition 8.3.x
DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.build-modl \
--target out \
--build-arg IGNITION_TAG=8.3 \
--build-arg BUILD_FOR_IGNITION_VERSION=8.3.2 \
--build-arg MIN_IGNITION_VERSION=8.3.0 \
--output type=local,dest=./docker-out/8.3 \
.Ignition 8.1.x
DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.build-modl \
--target out \
--build-arg IGNITION_TAG=8.1 \
--build-arg BUILD_FOR_IGNITION_VERSION=8.1.50 \
--build-arg MIN_IGNITION_VERSION=8.1.0 \
--output type=local,dest=./docker-out/8.1 \
.If the Ignition image uses a different install root than /usr/local/ignition, pass:
--build-arg IGNITION_HOME=/path/inside/container- Install Ignition 8.1.x and/or Ignition 8.3.x locally.
- Install JDK 17 (required for building the module).
On a default local install, the HTTP port is configured in:
- Ignition 8.3.x:
/usr/local/ignition/data/ignition.conf - Ignition 8.1.x:
/usr/local/ignition8.1/data/ignition.conf
To see what port is currently set:
grep -E '^(webserver\\.http\\.port|webserver\\.https\\.port)=' /usr/local/ignition/data/ignition.conf
grep -E '^(webserver\\.http\\.port|webserver\\.https\\.port)=' /usr/local/ignition8.1/data/ignition.confIgnition installs include an ignition.sh control script:
# Ignition 8.3.x
/usr/local/ignition/ignition.sh start
/usr/local/ignition/ignition.sh stop
/usr/local/ignition/ignition.sh status
# Ignition 8.1.x
/usr/local/ignition8.1/ignition.sh start
/usr/local/ignition8.1/ignition.sh stop
/usr/local/ignition8.1/ignition.sh statusIf you run into permissions errors starting/stopping, run the same commands with sudo.
All endpoints are under /system/zerobus:
GET /healthGET /diagnosticsPOST /configPOST /test-connectionPOST /ingest(single JSON event)POST /ingest/batch(JSON array of events)
module/src/main/java/com/example/ignition/zerobus/ZerobusGatewayHook.java: module lifecycle; loads/saves config; starts/stops services; registers HTTP endpoints under/system/zerobus/*.module/src/main/java/com/example/ignition/zerobus/TagSubscriptionService.java: tag event processing:- direct mode subscriptions via TagManager
- HTTP ingest queueing via
/ingestand/ingest/batch - batching + rate limiting + flush loop
module/src/main/java/com/example/ignition/zerobus/ZerobusClientManager.java: manages Zerobus client; converts events to protobuf and streams to Databricks.- Servlet compatibility layer:
.../web/ZerobusConfigServlet.javaselectsjavaxvsjakartaservlet implementation at runtime..../web/ZerobusServletHandler.javaholds shared request parsing and routing.
- Schema:
module/src/main/proto/ot_event.proto
Direct subscriptions
- Tag change event ->
TagSubscriptionServicelistener - Convert to internal
TagEvent-> queue - Flush loop batches ->
ZerobusClientManager - Protobuf (OTEvent) -> Zerobus stream -> Delta
HTTP ingest (Event Streams / external producer)
- Producer POSTs JSON ->
/system/zerobus/ingestor/ingest/batch - Handler parses + enqueues
TagEvents - Batching + streaming as above
- Ensure Node.js 20+ is installed:
node --version - Run
npm --prefix demo/frontend installfirst - Check port 5173 is not already in use
- Verify
.envhas correctDATABRICKS_HOST,DATABRICKS_TOKEN, andDATABRICKS_HTTP_PATH - Ensure the SQL warehouse is running in your Databricks workspace
- Check network connectivity:
curl -s https://<your-host>/api/2.0/clusters/list
- Ensure simulator dependencies are installed:
npm --prefix demo/simulator install - Check that SDT configuration defaults are set in
.env - Try running with verbose logging:
DEBUG=* npm --prefix demo/simulator run dev
- Run
cd demo && bash gates.shto see which specific gate is failing - Clear node_modules and reinstall:
rm -rf demo/*/node_modules && npm --prefix demo/frontend install && npm --prefix demo/simulator install - Ensure TypeScript version is 5.7+:
npx --prefix demo/frontend tsc --version
- Backend CORS is configured for
http://localhost:5173by default - Set
CORS_ORIGINSenv var to add additional origins (comma-separated)
