fujilib.registry¶
The register map and everything it is described in terms of (design §5). The generated register map lists every register.
fujilib.registry.registers ¶
The ZP-series register map: :class:RegisterSpec and the registry (design §5.1).
The map is written in Python, generated from the manual's stride patterns
(for c in 1..5, for r in 1..2), so it reads like the manual's tables
and mypy checks it. It is the single source of truth for addresses, allowed
function codes, value domains, scaling, safety tiers and evidence;
docs/registers.md is generated from it.
Addresses are 0-based relative addresses, exactly as on the wire. Page references are PDF page numbers of the manuals, which match the page markers of the text extracts (the printed page numbers differ).
What is writable. A holding register is writable only when its
declaration gives it a write tier. That is the reviewed subset of design §5.4:
documented, not contradicted by the bench unit, not an option, and testable on
the bench analyzer. Every other register, holding or input, is read-only. For
a writable register minimum and maximum are the limits of a write, the
narrower of the two manuals' where they disagree (the MODBUS manual defers
setting ranges to the instruction manual, TN5A1190a p.28).
The whole map is validated at import (:func:validate_map) and a violation
fails loudly as :class:~fujilib.errors.FujiConfigurationError:
- names are unique;
- no two entries overlap within a table;
- every entry lies inside a region for each function code it lists;
- every writable entry lies inside the frozen write envelope, is one word,
can be written with FC06, and is one of the reviewed settings of
:data:
~fujilib.registry.write_policy.REVIEWED_SETTINGS; - only documented registers are writable, and only above
READ_ONLY; - enum, limit and scaling metadata are consistent.
Access ¶
Bases: StrEnum
Whether fujilib may write a register. Operation commands are not registers.
LogField
dataclass
¶
One field of a log record, at offset words from the record start.
LogSpec
dataclass
¶
LogSpec(
name,
group,
table,
base,
record_words,
records,
fields,
evidence,
manual_ref,
doc,
channels=1,
channel_stride=0,
requires=Capability.NONE,
notes="",
)
A log of fixed-width records, newest first (design §2.6).
channels regions of records records each start channel_stride
words apart; record i (0 = newest) of channel c starts at
base + channel_stride * (c - 1) + record_words * i.
record_address ¶
First address of record index (0 = newest) of channel (1-based).
Raises:
| Type | Description |
|---|---|
FujiValidationError
|
|
Source code in src/fujilib/registry/registers.py
RegisterRegistry
dataclass
¶
The validated register map, indexed by name and by address.
Construction validates the map (:func:validate_map). The indexes are
read-only.
at ¶
groups ¶
has ¶
in_table ¶
log ¶
Return the log named name.
Raises:
| Type | Description |
|---|---|
FujiValidationError
|
no log has that name. |
Source code in src/fujilib/registry/registers.py
resolve ¶
Return the canonical spec named name.
Raises:
| Type | Description |
|---|---|
FujiValidationError
|
no register has that name. |
Source code in src/fujilib/registry/registers.py
select ¶
Every register whose name is prefix or starts with prefix., in map order.
Source code in src/fujilib/registry/registers.py
RegisterSpec
dataclass
¶
RegisterSpec(
name,
group,
table,
address,
dtype,
access,
read_functions,
write_functions,
safety,
evidence,
manual_ref,
doc,
count=1,
scaling=_NONE,
unit=None,
minimum=None,
maximum=None,
write_values=None,
write_percent_fs=None,
enum=None,
channel=None,
range=None,
alarm=None,
requires=Capability.NONE,
notes="",
)
One register (or a multi-word value) of the map.
minimum and maximum are raw limits, before scaling; for a writable
register they are the limits of a write. unit is a fixed display unit
("s", "%FS"); a scaled concentration takes its unit from the same
source as its decimals. notes records where the manuals disagree or the
bench unit contradicts them.
register_number
property
¶
The 40001- or 30001-based register number, for humans and docs.
write_percent_fs
class-attribute
instance-attribute
¶
For a range-scaled setting: the limits of a write, in percent of the range's full scale.
write_values
class-attribute
instance-attribute
¶
The raw values a write may use, where fewer than the limits allow.
Scaling
dataclass
¶
A scaling rule. The concentration unit comes from the same place as the decimals.
ScalingKind ¶
Bases: StrEnum
Where a register's decimal-point position (and concentration unit) comes from.
BY_ALARM_TARGET
class-attribute
instance-attribute
¶
The range registers of the alarm's target channel, whose encoding is contested.
BY_RANGE
class-attribute
instance-attribute
¶
The (channel, range) decimal-point and unit registers, 31087-31096 and 31067-31076.
FIXED
class-attribute
instance-attribute
¶
A constant number of decimals (the calibration deviation is %FS x 10).
INLINE
class-attribute
instance-attribute
¶
The two registers that follow it, read in the same block (concentrations).
validate_map ¶
Validate a register map; see the module docstring for the rules.
Takes the map as arguments so tests can feed it deliberately broken tables.
Raises:
| Type | Description |
|---|---|
FujiConfigurationError
|
the first rule the map breaks. |
Source code in src/fujilib/registry/registers.py
fujilib.registry.regions ¶
Valid address regions per function code (design §2.3).
A block read may bridge unused addresses inside a region but must never cross a region boundary: the bench unit answers a read that crosses a region's end with exception 03, and one that starts outside the map with exception 02 (design §2.2). The planner therefore closes a block at every region change.
Regions are listed per function code, because FC03 and FC04 address separate tables and FC06 covers a smaller holding range than FC10. Two kinds are kept apart:
- documented regions come from the MODBUS manual (TN5A1190a);
- observed regions were found on the bench unit and are only used for
registers behind a probed :class:
~fujilib.devices.capability.Capability.
A :class:RegionMap lists the regions fujilib reads from, not the whole
readable map; the analyzer also answers inside blocks fujilib never touches
(protocol findings §4). Being in a region never makes a register writable: the
write envelope is a separate frozen constant
(:mod:fujilib.registry.write_policy).
Evidence ¶
Bases: StrEnum
How much a fact about a register or region is worth (design §5.1).
CONTESTED
class-attribute
instance-attribute
¶
Documented, but the bench unit contradicts the manual. Kept raw; never writable.
INFERRED
class-attribute
instance-attribute
¶
An interpretation of what was observed or documented. Never writable.
OBSERVED
class-attribute
instance-attribute
¶
Seen on the bench unit (firmware 1.02) but not documented.
Region
dataclass
¶
A contiguous run of addresses one function code may access.
contains ¶
Whether count addresses from address all lie in this region.
RegionMap
dataclass
¶
The regions of one analyzer or profile, per function code.
Regions of the same function code must not overlap.
fujilib.registry.write_policy ¶
The only definition of what fujilib may ever write (design §5.4).
:data:WRITE_ENVELOPE is frozen and independent of the registry and of
probing: nothing is derived from the register map, and no observation can
widen it. The Modbus client re-checks every write request against it as the
last step before anymodbus, so a forged spec, a custom registry or an
address in a settings file cannot reach the wire outside it.
Deliberately excluded:
00A4h–00ABh, whose meaning (interference compensation coefficients) is inferred, not documented (design §2.6);- every key code at
07D0h(key simulation) but the six calibration keys of :data:CALIBRATION_KEYS. MODE and SIDE, which open the menus and enter their passwords, reach maintenance and factory mode (design §6.5), so the envelope checks the value written there as well as the address.
Inside the envelope, only :data:REVIEWED_SETTINGS are ever written: the
reviewed subset of design §5.4, by name and address, also written out here
independently of the registry. The registry refuses to mark anything else
writable, and a setting write is refused for anything not in it, so neither a
custom registry nor a forged spec can widen it.
The four operation commands are :class:OperationSpec entries, not registers:
each has its own facade method, safety tier and post-conditions, and none is
reachable by writing a parameter.
OperationSpec
dataclass
¶
One operation command: write value to address with FC06 (design §2.7).
WriteRange
dataclass
¶
Addresses first..last (inclusive) that function code fc may write.
values
class-attribute
instance-attribute
¶
The only words it may write, or None for any word.
allows ¶
Whether a write of count words at address with fc, of values, is allowed.
Where the range limits its values, the write is allowed only with
values given, count of them, each one of its values.
Source code in src/fujilib/registry/write_policy.py
contains ¶
Whether a write of count words at address with fc lies inside.
Source code in src/fujilib/registry/write_policy.py
check_envelope ¶
Refuse a write outside :data:WRITE_ENVELOPE.
Raises:
| Type | Description |
|---|---|
FujiValidationError
|
the write is outside the envelope. |
Source code in src/fujilib/registry/write_policy.py
envelope_allows ¶
Whether a count-word write at address with fc lies inside one envelope range.
Where a range limits the words written (key simulation), values must
be given and each be one of them: an address alone is not enough there.
Source code in src/fujilib/registry/write_policy.py
fujilib.registry.typecode ¶
The type code and the channel layout it implies (design §2.9).
The type code (FC04 0448h, one character per register) names the ordered configuration: which NDIR components, which O2 source, which O2-corrected outputs. The ZPA manual (§5.3(3), p.37) derives the channel layout from three of its digits:
- the NDIR components of digit 6, in order (NO is shown as NOx when digit 21 is A or C);
- then O2, when digit 7 names an O2 source;
- then the O2-corrected NO/SO2/CO values, when digit 21 is A or C;
- then their averages, when digit 21 is C.
The type code is a hint, not an authority. The bench unit's digit 7 says
"no O2" although channel 3 measures O2 with a cell fitted later, and three of
its digits are not in the current table. Labels that feed a calculation are
asserted by the caller; everything here is a suggestion with a
:class:~fujilib.registry.channels.LabelSource.
Options. Digits 21 (O2-corrected outputs) and 22 (the DIO contacts,
which carry the auto-calibration valve drive and the alarm outputs) say which
options were ordered. :attr:TypeCode.options is that suggestion; like a
label, it is a hint the caller can override by asserting the options.
:data:MODEL_OPTIONS is what each model's manual describes at all.
Only the ZPA code table ships. Other models' codes are kept raw.
ChannelLayout
dataclass
¶
The channels a type code implies, in channel order.
get ¶
The suggestion for channel, if the layout has one.
ChannelSuggestion
dataclass
¶
What the type code suggests a channel carries.
derived_from
class-attribute
instance-attribute
¶
For an O2-corrected value or average, the channel of the corrected component.
DigitMeaning
dataclass
¶
O2Correction ¶
O2Source ¶
Bases: StrEnum
Where the O2 value comes from (type-code digit 7).
EXTERNAL_ANALYZER
class-attribute
instance-attribute
¶
An external O2 analyzer through the A/I connector, as a 0-1 V DC signal.
EXTERNAL_ZIRCONIA
class-attribute
instance-attribute
¶
An external zirconia analyzer (ZFK7) through the same connector.
PARAMAGNETIC_ENVIRONMENTAL
class-attribute
instance-attribute
¶
A built-in paramagnetic cell, environmental-measurement variant.
PARAMAGNETIC_HEAT_TREATMENT
class-attribute
instance-attribute
¶
A built-in paramagnetic cell, heat-treatment variant.
TypeCode
dataclass
¶
TypeCode(
raw,
model=None,
decoder=None,
digits=(),
ndir_components=None,
o2_source=None,
o2_correction=None,
layout=None,
options=None,
)
A type code: the raw string plus whatever of it decodes.
TypeCodeDecoder ¶
Bases: Protocol
Decodes one model's type codes.
channel_layout ¶
Apply the ZPA manual's layout rule (§5.3(3)); see the module docstring.
Correction needs an O2 value, so without o2 no corrected channels are
laid out.
Source code in src/fujilib/registry/typecode.py
decode_type_code ¶
Decode raw with the table for its model, or keep it raw.
Never raises. A code whose first three characters name no known model
comes back with only raw (and model when it starts with ZP).
Source code in src/fujilib/registry/typecode.py
suggest_labels ¶
Suggest a label for each present channel (design §2.9, step 4).
A channel the type-code layout covers gets its suggestion. A present
channel just after the layout's NDIR components, when the layout has no
O2, is suggested as O2 by the layout rule, labelled INFERRED: this is
the bench unit, whose code says "no O2" while channel 3 measures O2. Any
other present channel gets no suggestion. A suggestion never selects a
scientific channel by itself; only an asserted label does.
Source code in src/fujilib/registry/typecode.py
fujilib.registry.channels ¶
Channel identifiers, roles, gases and label provenance (design §2.9, §8).
The ZP-series presents up to twelve display channels: the measured components first (NDIR, then O2), then O2-corrected values, corrected averages and an O2 average. Only channels 1–5 carry per-channel status registers (current range, calibration flags, errors, hold), so only they can be measured channels; 6–12 are always derived.
Which gas a channel carries is asserted by the caller. The type code and
live data only suggest it, and every label records its
:class:LabelSource so a consumer can tell the two apart.
ChannelId ¶
Bases: StrEnum
A display channel, CH1 … CH12.
is_measured
property
¶
Whether the channel has per-channel status registers (channels 1–5).
from_number
classmethod
¶
Return the channel with 1-based number.
Raises:
| Type | Description |
|---|---|
FujiValidationError
|
|
Source code in src/fujilib/registry/channels.py
ChannelRole ¶
Bases: StrEnum
What a channel's value is (ZPA manual §5.3(3)).
INSTANTANEOUS
class-attribute
instance-attribute
¶
A measured component: an NDIR gas or O2.
O2_AVERAGE
class-attribute
instance-attribute
¶
The moving average of O2. The manual shows it but not its channel index.
O2_CORRECTED
class-attribute
instance-attribute
¶
An NDIR value corrected to the O2 reference (type-code digit 21 = A or C).
O2_CORRECTED_AVERAGE
class-attribute
instance-attribute
¶
The moving average of an O2-corrected value (digit 21 = C).
UNKNOWN
class-attribute
instance-attribute
¶
Not established: no assertion and nothing the type code decodes.
Gas ¶
Bases: StrEnum
A measured gas.
Values are lower-case formulas, which is the vocabulary capa's cone
profile uses for its analyzers ("o2", "co", "co2").
LabelSource ¶
Bases: StrEnum
Where a channel's gas label came from (design §2.9).
ASSERTED
class-attribute
instance-attribute
¶
Given by the caller in a channel_map. The only source fit for calculation.
INFERRED
class-attribute
instance-attribute
¶
From the layout rule (NDIR components first, then O2). A weaker hint.
TYPE_CODE
class-attribute
instance-attribute
¶
Decoded from the analyzer's type code. A hint; the bench unit's code is stale.
coerce_channel ¶
Resolve channel to a :class:ChannelId.
Accepts a :class:ChannelId or a string such as "CH3", "ch3" or
"3".
Raises:
| Type | Description |
|---|---|
FujiValidationError
|
|
Source code in src/fujilib/registry/channels.py
coerce_channel_map ¶
Resolve a caller's channel map, as open_device and identify() take it.
Raises:
| Type | Description |
|---|---|
FujiValidationError
|
a channel or gas is unknown, a channel is asserted
as |
Source code in src/fujilib/registry/channels.py
coerce_gas ¶
Resolve gas to a :class:Gas, from a member or its formula in any case ("O2").
Raises:
| Type | Description |
|---|---|
FujiValidationError
|
|
Source code in src/fujilib/registry/channels.py
fujilib.registry.units ¶
Measurement units and the unit-code registers (design §2.5).
A concentration's unit is not fixed: it lives in a register beside the value (input 30001–30036) or in the range tables (31067–31076), as a code 0–3. The unit of a range cannot be changed (TN5A1190a p.26).
:func:unit_from_code and :func:coerce_unit never raise: an unrecognised
code or text becomes :attr:Unit.UNKNOWN, so one odd register never sinks a
whole frame. The pint mapping lives in :mod:fujilib.units.
Unit ¶
Bases: StrEnum
A concentration unit. The value is the canonical display string.
coerce_unit ¶
Map a :class:Unit or a unit string (case-insensitive) to a :class:Unit.
Unrecognised text becomes :attr:Unit.UNKNOWN.
Source code in src/fujilib/registry/units.py
unit_code ¶
Return the register code for unit.
Raises:
| Type | Description |
|---|---|
FujiValidationError
|
|
Source code in src/fujilib/registry/units.py
unit_from_code ¶
Map a unit-code register value (0 vol%, 1 ppm, 2 mg/m³, 3 g/m³) to a :class:Unit.
Any other value becomes :attr:Unit.UNKNOWN.
fujilib.registry.enums ¶
Enumerated register values, exactly as the manuals define them (design §2.6–§2.8).
Every member's value is the raw register value. Two traps are kept visible rather than merged:
- Alarm mode and alarm state are different enums. Mode 2 is "high or low"; state 2 is "low limit alarm", and state 0 is "no alarm".
- The two "cycle unit" registers use different codes. The auto-calibration,
auto-zero and blowback cycles use 0 = hours, 1 = days
(:class:
ScheduleCycleUnit); the moving-average and measurement-point periods use 0 = hours, 1 = minutes (:class:PeriodUnit).
Real analyzers do not always stay inside the documented domains (the bench
unit's alarm-6 target channel reads 12, outside the documented 0–6), so
decoders use :func:fujilib.protocol.modbus.codec.decode_enum, which can keep
an undefined value as a plain int instead of failing a whole read.
AlarmMode ¶
Bases: IntEnum
How an alarm is configured to trip (holding 40056–40060, 40131).
AlarmState ¶
Bases: IntEnum
Whether and how an alarm is currently tripped (input 30043–30047, 30191).
CalibrationKind ¶
CalibrationRangeMode ¶
Bases: IntEnum
Which ranges a calibration adjusts (holding 40031–40035).
BOTH calibrates both ranges together, manual or automatic, widening a
calibration beyond the range it was started on (ZPA manual p.45; design §2.6).
DayOfWeek ¶
Bases: IntEnum
Day of week in schedule start times (0 = Sunday … 6 = Saturday).
DisplayScreen ¶
Bases: IntEnum
The front panel's current screen (input 30181; TN5A1190a p.46).
MAINTENANCE and FACTORY mean an operator is in the service menus.
ErrorCode ¶
Bases: IntEnum
Analyzer error numbers (ZPA manual §8, service manual §4; design §2.8).
Errors 1, 2, 3 and 10 close the instrument-error (FAULT) contact; errors 4–9 close the calibration-error contact and are reported per channel.
AUTO_CALIBRATION
class-attribute
instance-attribute
¶
One of errors 4-8 occurred during auto calibration.
LIGHT_SOURCE
class-attribute
instance-attribute
¶
Light source or sector motor fault.
OUTPUT_CIRCUIT
class-attribute
instance-attribute
¶
Output cable or DIO circuit fault.
SPAN_AMOUNT_OVER_50
class-attribute
instance-attribute
¶
Span calibration amount over 50 %FS. The panel offers a forced calibration.
SPAN_OUT_OF_RANGE
class-attribute
instance-attribute
¶
Span calibration outside the allowable range.
ZERO_AMOUNT_OVER_50
class-attribute
instance-attribute
¶
Zero calibration amount over 50 %FS. The panel offers a forced calibration.
ZERO_OUT_OF_RANGE
class-attribute
instance-attribute
¶
Zero calibration outside the allowable range.
ErrorScope ¶
Bases: StrEnum
Whether an error concerns the whole analyzer or one channel.
HoldMode ¶
Bases: IntEnum
What the outputs hold during calibration (holding 40140).
KeyCode ¶
Bases: IntFlag
Front-panel key codes of the key-simulation register 42001.
The undocumented input 30190 (00BDh) shows the key being pressed at the panel in the same codes, and 0 otherwise (protocol findings §14.2).
fujilib writes only UP, DOWN, ESC, ENT, ZERO and SPAN, and only from the
front-panel driver of a manual zero or span (:mod:fujilib.devices.keys,
design §6.5). MODE and SIDE reach the menus and their passwords; the write
envelope refuses them.
ManualCalibrationResult ¶
Bases: IntEnum
The last manual calibration, as the undocumented input 30186 (00B9h) shows it.
Observed on the bench analyzer, not documented (protocol findings §14.2): 0 once a channel is selected, 4 while the calibration runs, 6 when it has finished, and kept until the next channel is selected. A calibration cancelled before it ran leaves 0. The value after a calibration error is not known.
NONE
class-attribute
instance-attribute
¶
None has finished since a channel was last selected (or it was cancelled).
ManualCalibrationStep ¶
Bases: IntEnum
The manual-calibration step shown on the panel (input 30182; TN5A1190a p.46).
Values 1–3 are not defined by the manual. On the bench analyzer the screen
register (30181) stays MEASUREMENT while one of these is shown
(protocol findings §14.3). On any other screen the word numbers the menu's
pages, these values among them, and is not a step (protocol findings §15).
MeasurementPoint ¶
Bases: IntEnum
Measurement-point setting (holding 40157; option).
PeriodUnit ¶
Bases: IntEnum
Unit of the moving-average and measurement-point periods (0 h, 1 min).
RangeIndex ¶
Bases: IntEnum
A range as the registers encode it: 0 is range 1, 1 is range 2.
RangeMethod ¶
Bases: IntEnum
How a channel changes range (holding 40111–40115).
AUTO switches up at 90 % of range 1 and back below 80 % (ZPA manual §6.1).
ScheduleCycleUnit ¶
Bases: IntEnum
Unit of the auto-calibration, auto-zero and blowback cycles (0 h, 1 days).
ZeroCalibrationMode ¶
Bases: IntEnum
Manual zero calibration scope (holding 40026–40030).
AT_ONCE zeroes every channel so set when any one is zeroed at the panel,
which widens a manual zero beyond the channel it was started on. Auto
calibration and auto zero calibration ignore it: they zero every enabled
channel together (ZPA manual p.47; design §2.6).