Python client for controlled Ignition Gateway automation through WebDev
Project description
fluxy
A Python library that emulates useful subsets of Ignition's system APIs from outside Ignition.
Install as a library:
uv add fluxy-ign
Install as a deployment tool:
uv tool install fluxy-ign
Install with MCP support:
uv tool install 'fluxy-ign[mcp]'
Authenticated WebDev deployment:
fluxy-deploy-webdev /path/to/ignition/data/projects/flux \
--auth-token-file /path/to/fluxy-token
Clients must pass the same bearer token:
fx = Fluxy("http://localhost:8088/system/webdev/flux", token="shared-secret-token")
See docs/auth.md for the full auth workflow, including token creation, project scan, client configuration, and troubleshooting 401/403 responses.
Run the optional MCP adapter:
fluxy-mcp \
--base-url http://localhost:8088/system/webdev/flux \
--token-file /path/to/fluxy-token
The MCP server is read-only by default. Add --allow-writes to expose write/configure tools and --allow-destructive to expose destructive tools such as tag deletion.
The PyPI distribution is fluxy-ign; the Python import remains fluxy.
Start with docs/user-guide.md for the user workflow. See docs/ for architecture, deployment, API, Gateway config, and integration-test details.
Initial scope:
fx.tag.read_blocking(tag_paths)fx.tag.write_blocking(tag_paths, values)fx.tag.copy(tag_paths, destination_path)fx.tag.configure(tags, base_path=None, collision_policy="o")fx.tag.browse(path=None, tag_filter=None)fx.tag.query(provider, query=None, limit=None, continuation=None)fx.alarmhelpers for tested disposable alarm query/shelve/unshelve/acknowledge workflowsfx.dbhelpers for Ignition datasource management, SQL queries, prepared queries, updates, transactions, and named queriesfx.devicehelpers for tested Ignition simulator-device add/list/enable/remove workflowsfx.historianhelpers for testedCore Historianbrowse and raw point injection/query workflowsfx.opchelpers for tested disposable simulator-device OPC browse/read/write workflowsfx.reporthelpers for tested report listing/execution workflowsfx.userhelpers for tested disposable user/role workflows againstuserDBfx.utilhelpers for tested gateway diagnostics and audit write/query workflowsfx.project.request_scan()python -m fluxy.gateway_configfor narrow Gateway SQLite/PostgreSQL connection resource deploymentfluxy-mcpoptional MCP server over the Fluxy API, read-only by default- HTTP bridge to Ignition WebDev endpoints that call the real
system.tag, testedsystem.alarm,system.db, testedsystem.device, testedsystem.historian, testedsystem.opc, and testedsystem.utilAPIs inside the gateway.
Snake_case is canonical for Python code. Ignition-style aliases are available for porting scripts:
fx.tag.readBlocking(...)fx.tag.writeBlocking(...)fx.project.requestScan()
Python Usage
from fluxy import Fluxy
fx = Fluxy(
base_url="https://ignition.example.com/system/webdev/<webdev-project>",
project_location="/usr/local/bin/ignition/data/projects/flux",
tag_provider="default",
token="shared-secret-token",
)
values = fx.tag.read_blocking([
"[default]Path/To/Tag1",
"[default]Path/To/Tag2",
])
for value in values:
print(value.tag_path, value.value, value.quality, value.timestamp)
Single-tag reads also work and return a single QualifiedValue:
value = fx.tag.read_blocking("[default]Path/To/Tag1")
Write/readback shape:
tag_paths = [
"[default]Demo/Area_A/Unit_01/LOAD_FACTOR",
]
qualities = fx.tag.write_blocking(tag_paths, [1.1])
values = fx.tag.read_blocking(tag_paths)
Single-tag writes also work and return a single WriteResult:
quality = fx.tag.write_blocking("[default]Path/To/Setpoint", 12.3)
Configure shape:
qualities = fx.tag.configure(
[
{
"name": "MemoryFloat",
"tagType": "AtomicTag",
"valueSource": "memory",
"dataType": "Float4",
"value": 1.0,
}
],
base_path="[default]Folder",
collision_policy="o",
)
Browse shape:
results = fx.tag.browse("[default]Folder")
for result in results:
print(result.name, result.full_path, result.tag_type, result.data_type)
Tag discovery shape:
missing = fx.tag.read_blocking("[default]Folder/MissingTag")
if str(missing.quality).startswith("Bad_DoesNotExist"):
print("tag is missing")
fx.tag.copy("[default]Folder/MemoryFloat", "[default]Folder/Copies")
results = fx.tag.query("default", query={"condition": {"path": "*MemoryFloat*"}})
system.tag.exists is forbidden in Fluxy. Use read_blocking(...) and monitor for Bad_DoesNotExist instead; it is more performant because it stays on the normal tag-read path instead of adding a dedicated gateway call.
Alarm shape:
source = "prov:default:/tag:FluxyAlarmIntegration/AlarmFloat:/alm:HighAlarm"
rows = fx.alarm.query_status(source=[source], include_shelved=True)
fx.alarm.shelve([source], timeout_seconds=60)
shelved = fx.alarm.get_shelved_paths()
fx.alarm.unshelve([source])
Dataset-like database results return QueryResult, a list[dict[str, Any]] subclass with boundary metadata. Ignition Datasets are serialized as columns + rows by WebDev and converted to row mappings in Python. See docs/user-guide.md#dataset-results.
Live DB tests cover 256-row mixed SQLite datasets. Integers/reals/text/nulls preserve JSON-compatible values, SQLite boolean-like values return 0/1, epoch milliseconds remain integers, and BLOBs return byte-value lists.
Device management shape:
fx.device.add_device("Simulator", "FluxyTemporarySimulator", {"Enabled": 0})
devices = fx.device.list_devices()
fx.device.set_device_enabled("FluxyTemporarySimulator", False)
fx.device.remove_device("FluxyTemporarySimulator")
Historian shape:
path = "histprov:Core Historian:/sys:gateway:/prov:default:/tag:FluxyHistorianIntegration/Test"
fx.historian.store_data_points([path], [12.5], [1778545000000], [192])
rows = fx.historian.query_raw_points([path], 1778544990000, 1778545010000)
aggregated = fx.historian.query_aggregated_points(
[path],
1778544990000,
1778545010000,
aggregates=["Maximum"],
column_names=["value"],
)
browse_results = fx.historian.browse(
"histprov:Core Historian:/sys:gateway:/prov:default:/tag:FluxyHistorianIntegration"
)
fx.historian.store_metadata([path], [1778545000000], {"documentation": "checked"})
metadata = fx.historian.query_metadata([path], start_date=1778544990000, end_date=1778545010000)
qualities = fx.historian.store_annotations(
[path], [1778544995000], end_times=[1778545005000], types=["note"], data=["checked"]
)
annotations = fx.historian.query_annotations([path], 1778544990000, end_date=1778545010000)
fx.historian.delete_annotations([path], [annotations[0].storage_id])
The historian WebDev bridge selects system.historian.* on Ignition 8.3+ and falls back to the 8.1 system.tag.* historian functions when needed. Prefer 8.3 sys/prov historical paths in Python; the 8.1 storage fallback maps them to legacy storeTagHistory arguments.
Live tag-to-history boundary tests show Core Historian raw data points are numeric-oriented: Boolean stores as 1.0/0.0, numeric tags round-trip numerically, DateTime stores as epoch milliseconds, String does not query back through queryRawPoints despite a Good store quality, and Document is rejected by storeDataPoints.
OPC shape:
server = "Ignition OPC UA Server"
servers = fx.opc.get_servers(include_disabled=True)
rows = fx.opc.browse(opc_server=server, device="FluxyOpcSimulator")
device_nodes = fx.opc.browse_server(server, "Devices")
simple_rows = fx.opc.browse_simple(opc_server=server, device="FluxyOpcSimulator")
value = fx.opc.read_value(server, rows[0]["opcItemPath"])
quality = fx.opc.write_value(server, rows[0]["opcItemPath"], 123)
Util diagnostics and audit shape:
version = fx.util.get_version()
modules = fx.util.get_modules()
fx.util.audit("FluxyIntegrationAudit", action_target="unique", audit_profile="Audit")
rows = fx.util.query_audit_log("Audit", action_filter="FluxyIntegrationAudit")
fx.util.get_version() is cached after the first gateway read. Use fx.util.refresh_version() after changing gateway versions.
Report shape:
project = fx.project.get_project_name()
reports = fx.report.get_report_names_as_list(project)
report_rows = fx.report.get_report_names_as_dataset(project)
pdf = fx.report.execute_report("test_Report", project, file_type="pdf")
User source shape:
sources = fx.user.get_user_sources()
fx.user.add_role("UserDB", "fluxy_role")
fx.user.edit_role("UserDB", "fluxy_role", "fluxy_role_edited")
fx.user.add_user("UserDB", "fluxy_user", "password", roles=["fluxy_role"])
user = fx.user.get_user("UserDB", "fluxy_user")
fx.user.remove_user("UserDB", "fluxy_user")
fx.user.remove_role("UserDB", "fluxy_role_edited")
fx.user.add_schedule("fluxy_schedule", source_schedule="Always")
fx.user.remove_schedule("fluxy_schedule")
fx.user.add_holiday("fluxy_holiday", 2114904400000)
fx.user.remove_holiday("fluxy_holiday")
Project scan shape:
result = fx.project.request_scan()
print(result.ok, result.message)
WebDev Contract
Read request:
{
"tagPaths": ["[default]Path/To/Tag"],
"timeoutMs": 45000
}
tagPaths is the canonical request key. The Ignition WebDev bridge also accepts tag_paths and tag_list as compatibility aliases for existing generated files.
Configure request:
{
"basePath": "[default]Folder",
"tags": [
{
"name": "MemoryFloat",
"tagType": "AtomicTag",
"valueSource": "memory",
"dataType": "Float4",
"value": 1.0
}
],
"collisionPolicy": "o"
}
Read response:
{
"ok": true,
"values": [
{
"tagPath": "[default]Path/To/Tag",
"value": 1.23,
"quality": "Good",
"timestamp": "2026-05-11T12:00:00.000Z"
}
]
}
Install For Local Development
uv sync
uv run pytest
Ignition Setup
Create a WebDev project named Fluxy or adjust base_url to your chosen path.
Deploy the package-owned WebDev resource library into an Ignition project with:
uv run python -m fluxy.deploy_webdev /path/to/ignition/data/projects/flux
The WebDev resources are grouped under fluxy/ so this library can be tracked independently of other WebDev resources:
/fluxy/tag/readBlocking/fluxy/tag/writeBlocking/fluxy/tag/copy/fluxy/tag/configure/fluxy/tag/browse/fluxy/tag/queryTags/fluxy/alarm/queryStatus/fluxy/alarm/shelve/fluxy/alarm/unshelve/fluxy/alarm/getShelvedPaths/fluxy/alarm/acknowledge/fluxy/db/.../fluxy/device/listDevices/fluxy/device/addDevice/fluxy/device/removeDevice/fluxy/device/setDeviceEnabled/fluxy/historian/browse/fluxy/historian/storeDataPoints/fluxy/historian/queryRawPoints/fluxy/historian/queryAggregatedPoints/fluxy/historian/storeAnnotations/fluxy/historian/queryAnnotations/fluxy/historian/deleteAnnotations/fluxy/historian/storeMetadata/fluxy/historian/queryMetadata/fluxy/opc/getServers/fluxy/opc/getServerState/fluxy/opc/browse/fluxy/opc/browseServer/fluxy/opc/browseSimple/fluxy/opc/readValue/fluxy/opc/readValues/fluxy/opc/writeValue/fluxy/opc/writeValues/fluxy/util/getVersion/fluxy/util/getModules/fluxy/util/getGatewayStatus/fluxy/util/getProjectName/fluxy/util/audit/fluxy/util/queryAuditLog/fluxy/report/getReportNamesAsList/fluxy/report/getReportNamesAsDataset/fluxy/report/executeReport/fluxy/user/getUserSources/fluxy/user/getRoles/fluxy/user/addRole/fluxy/user/editRole/fluxy/user/removeRole/fluxy/user/addUser/fluxy/user/getUser/fluxy/user/getUsers/fluxy/user/editUser/fluxy/user/removeUser/fluxy/user/addSchedule/fluxy/user/getSchedule/fluxy/user/getSchedules/fluxy/user/removeSchedule/fluxy/user/addHoliday/fluxy/user/getHoliday/fluxy/user/getHolidays/fluxy/user/removeHoliday/fluxy/project/requestScan
If editing the WebDev doPost section manually in Designer, use the body-only script instead:
ignition_webdev/designer_body/readBlocking_doPost_body.py
Designer adds def doPost(request, session): automatically. Do not paste a second function definition into that editor.
If AUTH_TOKEN is set in the WebDev scripts, configure the same token in Python:
from fluxy import Fluxy
fx = Fluxy(base_url="https://host/system/webdev/<webdev-project>", token="same-token")
Security note: writeBlocking is powerful. The current integration tests create disposable memory tags. Before using this against real operational tags, add gateway-side auth plus an allowlist.
There is also a controlled memory-tag write/readback probe for local trials:
uv run python scripts/write_readback_generated_tags.py \
--base-url "http://localhost:8088/system/webdev/flux"
Integration Trial
The generated tag probe reads a tiny sample of full Ignition tag paths through the live WebDev bridge.
Generated-tag interface contract:
- Input files provide full Ignition tag path strings.
- Paths include providers, for example
[default]Demo/Area_A/.../LOAD_FACTOR. fluxysends them to WebDev as JSON under the canonicaltagPathskey.- WebDev runs
system.tag.readBlocking(tag_paths, timeout_ms)inside Ignition and returnsvalue,quality, andtimestampper path.
-
Deploy the WebDev resources with
fluxy.deploy_webdev, then callfx.project.request_scan(). -
Confirm the project exposes:
https://host/system/webdev/<webdev-project>/fluxy/tag/readBlocking
https://host/system/webdev/<webdev-project>/fluxy/tag/writeBlocking
https://host/system/webdev/<webdev-project>/fluxy/tag/configure
https://host/system/webdev/<webdev-project>/fluxy/tag/browse
https://host/system/webdev/<webdev-project>/fluxy/tag/queryTags
https://host/system/webdev/<webdev-project>/fluxy/db/getConnections
https://host/system/webdev/<webdev-project>/fluxy/project/requestScan
If /system/webdev/<webdev-project> returns Resource "null" not found, that only means the project root was reached without a resource name. Test /fluxy/tag/readBlocking instead.
- Run the generated-tag probe:
uv run python scripts/read_generated_tags.py \
--base-url "https://host/system/webdev/<webdev-project>" \
--sample-size 3
Optional environment-based version:
FLUXY_BASE_URL="https://host/system/webdev/<webdev-project>" \
FLUXY_SAMPLE_SIZE=3 \
uv run python scripts/read_generated_tags.py
The normal pytest suite includes closed-loop live gateway integration tests:
FLUXY_BASE_URL="https://host/system/webdev/<webdev-project>" \
uv run pytest tests/test_integration_generated_tags.py
The write/readback test creates disposable memory tags and removes them during cleanup:
FLUXY_BASE_URL="https://host/system/webdev/<webdev-project>" \
uv run pytest tests/test_integration_generated_write_readback.py
The configure integration test creates memory tags, reads their configured values, writes new values, and reads them back:
FLUXY_BASE_URL="https://host/system/webdev/<webdev-project>" \
FLUXY_CONFIGURE_BASE_PATH="[default]FluxyDemo" \
uv run pytest tests/test_integration_configure_types.py
The browse integration test inspects those configured memory tags:
FLUXY_BASE_URL="https://host/system/webdev/<webdev-project>" \
FLUXY_CONFIGURE_BASE_PATH="[default]FluxyDemo" \
uv run pytest tests/test_integration_browse_configured_tags.py
Useful optional variables:
FLUXY_TOKEN: bearer token ifAUTH_TOKENis configured in WebDev.FLUXY_TAG_PATHS_FILE: override the generated tag path file.FLUXY_SAMPLE_SIZE: number of tag paths to read, default3.FLUXY_TIMEOUT_MS: Ignition read timeout, default45000.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fluxy_ign-0.1.1.tar.gz.
File metadata
- Download URL: fluxy_ign-0.1.1.tar.gz
- Upload date:
- Size: 113.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
698f1cec952d07480424d01def2f747eab29df4e0bf0f6f07b702ba733aee7cc
|
|
| MD5 |
46ce69c0638c8cbe81efa54f04354a77
|
|
| BLAKE2b-256 |
99d2c073981aede6af00cb84d027b10b837e6236520695ab4236e3eb9b5f3709
|
File details
Details for the file fluxy_ign-0.1.1-py3-none-any.whl.
File metadata
- Download URL: fluxy_ign-0.1.1-py3-none-any.whl
- Upload date:
- Size: 77.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bce18d85096ba75e7acbf652ba607e19abf9b606e2717bb4875f57ed9233c0c7
|
|
| MD5 |
9209ed05590fe46337542861c47d086d
|
|
| BLAKE2b-256 |
ef82a8c8ed1a30d01213a12e72375558ca3252be94b7a3936493b8dd9be7d932
|