fujilib
Async-first Python driver for Fuji Electric ZP-series NDIR gas analyzers (ZPA, and the ZPB / ZPG / ZPAJ / ZPG3E models that share its MODBUS map), built on
anyserialandanymodbus.
fujilib is a member of the *lib instrument-driver family (alicatlib,
sartoriuslib, watlowlib, servomexlib, …). It shares their entry point,
frozen models, error hierarchy, streaming / sinks / sync / CLI conventions and
the unified device-library API; its internals are shaped to this analyzer.
Status
Alpha. 0.1.0 is the first release. The read-only API works against the
development analyzer: open_device(), identification, polls with validity,
metadata, settings, logs, discovery, recording at a fixed rate to memory, CSV
or Parquet, a blocking facade, and the fuji-read, fuji-discover,
fuji-configure, fuji-decode, fuji-stream, fuji-capture and fuji-diag
commands. A reviewed subset of settings writes, settings documents, return to
measurement, and manual zeros and spans watched at the panel or driven from the
host (fuji-calibrate) work on the development analyzer too; auto calibration
and auto zero have run only on the simulated analyzer, since the development
analyzer has no calibration valves. The documentation is at
https://fujilib.graysonbellamy.dev/. See docs/design.md
for the architecture and the phased plan, and CHANGELOG.md
for what has landed.
import anyio
from fujilib import open_device
async def main() -> None:
async with await open_device(
"COM8", channel_map={"CH1": "co2", "CH2": "co", "CH3": "o2"}
) as anz:
frame = await anz.poll() # every channel and the analyzer status, 2 transactions
o2 = frame.channel("CH3")
print(o2.value, o2.unit, o2.state) # 20.2 vol% ok
anyio.run(main)
Record to a file from the command line (Parquet needs fujilib[parquet]):
fuji-capture COM8 --gas CH1=co2 --gas CH2=co --gas CH3=o2 --out run.parquet
The first release, 0.1.0, covers:
- monitoring, metadata and acquisition.
poll()returns every channel with its hold, calibration and error state in two Modbus transactions, and the library streams and records to memory, CSV or Parquet; - a reviewed subset of settings writes and the documented operation commands
(auto calibration, auto zero, blowback, return to measurement), behind
confirm=True, pre-I/O validation and read-back verification; - a record of every manual zero and span made at the front panel while it is connected, since the analyzer keeps no calibration log on older firmware;
- a manual zero or span driven from the host (
fuji-calibrate): fujilib presses the calibration keys while the operator switches the gas valves, waits for the reading to settle on the gas named, and records the run.
Design points
- Validity and provenance travel with every value. Hold, calibration and error state, and where each channel's gas label came from, are carried into every reading and row. Unknown validity stays unknown.
- Gas labels that feed a calculation are asserted by the caller. The analyzer's type code is treated as a hint, not an authority.
- Writes are hard to get wrong. Everything fujilib may ever write is a frozen envelope of documented user settings and operation commands. Factory parameters are out of scope, and the only front-panel keys fujilib presses are the six of a manual zero or span, never the keys into the menus.
- No hardware needed to develop or test. A simulated analyzer runs the full stack in CI.
Oxygen measurements
Over Modbus the analyzer reports O2 with the display's resolution: 0.01 vol% (100 ppm) per step on the development unit. Modbus O2 is not validated for oxygen-consumption calorimetry. See design §2.11.
Installation
Requires Python 3.13+.
pip install fujilib
pip install "fujilib[parquet]" # with the Parquet sink
From source:
git clone https://github.com/GraysonBellamy/fujilib
cd fujilib
uv sync --all-extras --dev
Development
uv run pre-commit install
uv run ruff format --check .
uv run ruff check .
uv run mypy
uv run pyright
uv run pytest
See CONTRIBUTING.md.
License
MIT. See LICENSE.
Metadata
Release files for fujilib 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fujilib-0.1.0.tar.gz | 552.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fujilib-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 842.6 kB
Release files / fujilib-0.1.0.tar.gz
| Download URL | fujilib-0.1.0.tar.gz |
|---|---|
| Size | 552.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
055cc416cfb0656c64565240ac559d73af69019c018586a75a5547a6892cd64b
|
|
BLAKE2b-256 checksum How to use checksums |
b67e58d680cdfad8c776bdfba5cc57b330b317406513d3c2fcc3a111599a54f1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency logRelease files / fujilib-0.1.0-py3-none-any.whl
| Download URL | fujilib-0.1.0-py3-none-any.whl |
|---|---|
| Size | 289.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0694257daf75474363a7bfb56e9409bc5e9e87a31a55880d87e1300696b5ceed
|
|
BLAKE2b-256 checksum How to use checksums |
3f7bc7d493e93b7351135dd017b3e44d79b5aaf7193e5f1be8d7aa24f819af2e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency log