Skip to content

Component reference

The complete API of the modelling layer’s component classes and the Device base class, importable from modbus_connection.model (the SunSpec section from modbus_connection.model.sunspec). The fields declared on them are in the field reference.

Maps a device sub-system to typed register and bit attributes. Subclass it and declare fields as class attributes. See the overview for the full guide.

Component(unit, index=1, *, base_offset=0)
Parameter Type Meaning
unit ModbusUnit The unit handle to read from and write to.
index int, default 1 1-based instance index for a layout with per-field stride. See Placing a component.
base_offset int, default 0 Shift applied to every address the component touches, placing the whole declared layout at another base address.

The configuration attributes are overridable on a subclass (or set per instance). declared_fields and resolved_fields are derived from the declarations and read-only.

Attribute Type Default Meaning
register_space "holding" | "input" "holding" The register space this component’s register fields are read from (FC03 / FC04).
register_ranges tuple[Range, ...] | None None The addresses the device answers in the component’s register space: a read may merge freely inside a range and never crosses a boundary. None falls back to gap-based planning. Stated in declared coordinates, so they shift with base_offset.
coil_ranges tuple[Range, ...] | None None Readable ranges in the coil space (FC01).
discrete_ranges tuple[Range, ...] | None None Readable ranges in the discrete-input space (FC02).
max_gap int 16 Gap-based planning only: spans within this many addresses merge into one read.
max_span int 125 The widest a single block read may be (125 is the Modbus per-request ceiling).
scale_in_block bool False On a repeating sub-unit: shift scale_register addresses with each instance instead of keeping them in the parent’s fixed block.
declared_fields Mapping[str, RegisterField | CoilField | DiscreteInputField] derived Read-only mapping of attribute name to declared field object, in declaration order; available on the class and its instances, and never narrowed by restrict_fields.
resolved_fields Mapping[str, ResolvedField] derived Read-only mapping of attribute name to where the field sits on the device, in declaration order. Per instance, so it carries a repeated sub-unit’s shift, and narrowed by restrict_fields to what the component reads.

async. Read every field with pooled block reads, decode the values, and notify the listeners. Pass notify=False for a caller that notifies them itself. The read plan is built and cached on the first call. If the device rejects a block, this raises the typed exception for the code, with the refused block on .block, and the update applies nothing.

async. Resize, place and update only the poll-time repeating_group fields: those counted by a register or placed by a callable. The values they depend on must already have been read. async_update() does this as its second pass. Call it directly only to refresh the groups alone.

async. Run the same reads as async_update(), refreshing the fields and firing listeners, and additionally return the raw words and bits as {space: {address: value}}. The result is keyed by the four Modbus spaces ("holding", "input", "coil", "discrete"), addresses ascending. Raises the typed exception if the device rejects a block, like async_update(). notify=False skips the listeners. The fields still refresh.

async. Write a writable register or coil by attribute name. A register write uses FC06 for a single word and FC16 for multiple (or always FC16 with force_fc16). A coil write uses FC05. A validator set as writable vets the value first, and a dynamically-scaled field reads its scale factor fresh in the same write. Raises AttributeError for an unknown or read-only field (input registers and discrete inputs are always read-only) and ValueError if the value cannot be scaled.

The ModbusUnit this component reads from and writes to. Also set on the sub-instances a repeating_group builds.

Narrow this component to the fields in names and reshape its read plan so no block spans an excluded field’s registers. Excluded fields read as None and can no longer be written. Raises ValueError for an unknown field name, or if the component declares a repeating_group (not supported). See Restricting fields.

Register a Callable[[], None] fired after each update. Returns an unsubscribe callable.

Fire this component’s update listeners, and each repeating-group instance’s. async_update() calls it for you.

Pools reads for several components on one unit. See Component groups.

ComponentGroup(unit, components)
Parameter Type Meaning
unit ModbusUnit The unit every component is read from.
components Iterable[Component] The members. Their resolved readable ranges must agree per space where they overlap, and all must share max_span. A conflict raises ValueError.

async. Refresh every member with pooled block reads, then size and refresh each member’s register-counted repeating groups. Fires each member’s listeners unless notify=False. Raises the typed exception if the device rejects any block.

async. Like Component.async_read_raw(), merged across the members: the pooled reads run, members refresh and notify, and the raw {space: {address: value}} map comes back. notify=False skips the members’ listeners.

Fire each member component’s update listeners.

A component whose layout is built at runtime instead of declared on a class. See Manual components. Addresses are absolute (no index / stride / base_offset).

ManualComponent(unit, *, max_gap=16, max_span=125, holding_ranges=None,
input_ranges=None, coil_ranges=None, discrete_ranges=None)
Parameter Type Meaning
unit ModbusUnit The unit to read from and write to.
max_gap / max_span int The planning limits, per instance.
holding_ranges / input_ranges / coil_ranges / discrete_ranges tuple[Range, ...] | None Readable ranges per table. A table left None falls back to gap-based planning.

Add a read target under key, replacing any existing one and invalidating the cached plan. target is a RegisterField, a bit field (coil / discrete_input), or a repeating_group. space selects "holding" (default) or "input" for a register target; passing it for a bit field or a repeating_group raises ValueError, as does an unknown space. An unsupported target type raises TypeError.

Remove the target under key and any value read for it. Invalidates the cached plan. Removing an unknown key is a no-op.

The value decoded for key on the last update, or None if not yet read. For a repeating_group key, the list of instances.

Property. A copy of all decoded values from the last update as dict[str, Any] (repeating-group instances not included).

async. Read every target with pooled reads and return the decoded values as a dict. notify=False skips the listeners, for a caller that notifies them itself. Raises the typed exception if the device rejects a block.

async. Write a writable register or coil by key, with the same behaviour and errors as Component.write (AttributeError for an unknown or read-only key).

async_read_raw(), async_update_repeating_groups(), add_update_listener(listener), and notify() work exactly as on Component.

The base class for a library’s top-level device object. A subclass holds one Component (or ComponentGroup) per sub-system as an attribute and overrides _async_setup(). See The device object.

Device(unit)
Parameter Type Meaning
unit ModbusUnit The unit every sub-system is read from. Stored as modbus_unit.

async. The hook a subclass overrides to read what never changes and settle which optional sub-systems the device has. The default does nothing.

async. Run _async_setup() if it has not completed yet. A run that raised does not count, so the next call runs it again. async_poll() and async_read_raw() call it first.

async. Ensure setup, then read each sub-system in names on its own with async_update(notify=False), where each name is an attribute of the device. Returns an UpdateReport: a sub-system that refreshed is added to updated, one that raised a ModbusError is recorded under failed. Pass report to add to an earlier poll’s report instead of a new one. Listeners of every refreshed sub-system fire once the whole poll is done. An attribute that is None is skipped.

Two errors propagate instead of being recorded. A ModbusConnectionError propagates at once. A ModbusTimeoutError propagates while the report holds nothing under updated or failed, so a device that answers nothing surfaces as a timeout.

async. Ensure setup, then read each sub-system in names with async_read_raw(notify=False) and merge the results into one Raw map, keyed by the four Modbus spaces with addresses ascending, like Component.async_read_raw(). An attribute that is None is skipped. Raises the same ModbusError subclasses as an update.

The ModbusUnit passed to the constructor.

A dataclass describing what one async_poll() refreshed:

Field Type Meaning
updated set[str] The attribute names of the sub-systems that refreshed.
failed dict[str, ModbusError] The sub-systems that raised, with the error each raised.

Both default to empty, so UpdateReport() starts a fresh report. complete is True while failed is empty.

async. Call async_update() on component and return it. Returns None if the device answers with IllegalDataAddressError or IllegalFunctionError, the codes a device uses to refuse a sub-system it does not have. Any other ModbusError propagates. The return type follows the argument, so await read_optional(HotWater(unit)) is typed HotWater | None.

dict[str, dict[int, int | bool]]. A raw read result, grouped {space: {address: value}} with the addresses of each space ascending.

tuple[int, int]. An inclusive (low, high) readable address range.

A frozen dataclass locating one field, returned in resolved_fields:

Field Type Meaning
field RegisterField | CoilField | DiscreteInputField The declared field object, carrying encode(), writable and the rest.
address int Absolute address of its first register or bit.
count int Registers it spans; always 1 for a bit.
scale_address int | None Absolute address of its scale register, or None.
space RegisterSpace | BitSpace The space it is read from and written to.

Literal["input", "holding"]. Which register space a field is read from (FC04 / FC03).

Literal["coil", "discrete"]. Which bit space a bit field is read from (FC01 / FC02).

Callable[[], None]. The callback type add_update_listener takes.

From modbus_connection.model.sunspec. See SunSpec discovery for the guide and the field reference for the point helpers.

async. Walk the SunSpec model chain starting at the "SunS" marker at base_address and return the discovered models as a SunSpecModels, keyed by model ID (an ID can occur more than once). Raises SunSpecError if the marker is absent or the chain does not terminate within 100 models.

The scan result: a dict[int, list[SunSpecModel]] subclass, usable as a plain dict, with lookup helpers on top.

  • first(*model_ids) returns the first discovered SunSpecModel among model_ids. The IDs are tried in the order given, so earlier IDs take priority (preferred model variants before their fallbacks). For an ID discovered more than once, the first location in chain order is returned. Returns None when no ID matches.
  • chain returns every discovered model in chain order, as a list[SunSpecModel] ascending by address. For a device that repeats a model ID, this is what distinguishes the repeats.
  • at(address) returns the model whose header sits at address, or None. An address inside a model’s block is not a match.

A frozen dataclass locating one discovered model:

Field Type Meaning
model_id int The SunSpec model ID.
address int The address of the model’s two-register header.
length int The data length in registers, as the header reports it, excluding the header.
span int The registers the whole block occupies (length + 2): the count that reads the model, and the step to the next header.

A Component subclass placed at a discovered model’s address. It declares the model header as two fields of its own: model_id at offset 0 and model_length at offset 1. Subclasses declare their points at header-relative offsets. Data starts at offset 2.

SunSpecComponent(unit, model)

model is the SunSpecModel from a scan. It becomes the component’s base_offset. Every read verifies the read-back header against the discovered model and raises SunSpecMapShiftError on a mismatch.

restrict_fields(names) keeps model_id and model_length whether or not names lists them, since the header is what that verification reads.

model_length_group(component_class, *, start, stride)

Section titled “model_length_group(component_class, *, start, stride)”

Declare a fixed-width trailing group on a SunSpecComponent. Returns a RepeatingGroupField. The instances are available when the component is constructed and use its normal read plan.

Argument Meaning
component_class A Component subclass with fields at instance-0 addresses.
start Offset of the first block from the model header, including the two header registers. Must be at least 2.
stride Registers per block. Must be positive.

The count is (model.length + 2 - start) / stride. A model ending at start has zero instances. The constructor raises SunSpecError for a negative count or an incomplete final block. Each component binds its own count, so one generated class can represent devices with different model lengths.

Invalid start or stride arguments raise ValueError when the group is declared. This helper is for groups directly on a SunSpecComponent. It does not support groups nested inside another repeated block.

Raised when a device does not behave like a SunSpec device. Subclasses Exception rather than ModbusError.

SunSpecError subclass: a SunSpecComponent’s model header no longer matches its discovered location, so the register map has changed. Rescan and rebuild the components.

Raised by the generator (modbus_connection.model.sunspec.generate) when emitting a static layout would be incorrect.