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.
Component
Section titled “Component”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. |
Class attributes
Section titled “Class attributes”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. |
Methods
Section titled “Methods”async_update(*, notify=True)
Section titled “async_update(*, notify=True)”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_update_repeating_groups()
Section titled “async_update_repeating_groups()”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_read_raw(*, notify=True)
Section titled “async_read_raw(*, notify=True)”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.
write(field, value)
Section titled “write(field, value)”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.
modbus_unit
Section titled “modbus_unit”The ModbusUnit this
component reads from and writes to. Also set on the sub-instances a
repeating_group builds.
restrict_fields(names)
Section titled “restrict_fields(names)”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.
add_update_listener(listener)
Section titled “add_update_listener(listener)”Register a Callable[[], None] fired after each update. Returns an unsubscribe
callable.
notify()
Section titled “notify()”Fire this component’s update listeners, and each repeating-group instance’s.
async_update() calls it for you.
ComponentGroup
Section titled “ComponentGroup”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. |
Methods
Section titled “Methods”async_update(*, notify=True)
Section titled “async_update(*, notify=True)”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_read_raw(*, notify=True)
Section titled “async_read_raw(*, notify=True)”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.
notify()
Section titled “notify()”Fire each member component’s update listeners.
ManualComponent
Section titled “ManualComponent”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. |
Methods
Section titled “Methods”add(key, target, *, space=None)
Section titled “add(key, target, *, space=None)”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(key)
Section titled “remove(key)”Remove the target under key and any value read for it. Invalidates the
cached plan. Removing an unknown key is a no-op.
get(key)
Section titled “get(key)”The value decoded for key on the last update, or None if not yet read. For
a repeating_group key, the list of instances.
values
Section titled “values”Property. A copy of all decoded values from the last update as
dict[str, Any] (repeating-group instances not included).
async_update(*, notify=True)
Section titled “async_update(*, notify=True)”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.
write(key, value)
Section titled “write(key, value)”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).
Shared with Component
Section titled “Shared with Component”async_read_raw(), async_update_repeating_groups(),
add_update_listener(listener), and notify() work exactly as on
Component.
Device
Section titled “Device”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. |
Methods
Section titled “Methods”_async_setup()
Section titled “_async_setup()”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_ensure_setup()
Section titled “async_ensure_setup()”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_poll(names, report=None)
Section titled “async_poll(names, report=None)”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_read_raw(names)
Section titled “async_read_raw(names)”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.
modbus_unit
Section titled “modbus_unit”The ModbusUnit passed
to the constructor.
UpdateReport
Section titled “UpdateReport”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.
read_optional(component)
Section titled “read_optional(component)”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.
Supporting types
Section titled “Supporting types”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.
ResolvedField
Section titled “ResolvedField”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. |
RegisterSpace
Section titled “RegisterSpace”Literal["input", "holding"]. Which register space a field is read from
(FC04 / FC03).
BitSpace
Section titled “BitSpace”Literal["coil", "discrete"]. Which bit space a bit field is read from
(FC01 / FC02).
UpdateListener
Section titled “UpdateListener”Callable[[], None]. The callback type add_update_listener takes.
SunSpec discovery and components
Section titled “SunSpec discovery and components”From modbus_connection.model.sunspec. See
SunSpec discovery for the
guide and the
field reference
for the point helpers.
scan(unit, base_address)
Section titled “scan(unit, base_address)”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.
SunSpecModels
Section titled “SunSpecModels”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 discoveredSunSpecModelamongmodel_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. ReturnsNonewhen no ID matches.chainreturns every discovered model in chain order, as alist[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 ataddress, orNone. An address inside a model’s block is not a match.
SunSpecModel
Section titled “SunSpecModel”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. |
SunSpecComponent
Section titled “SunSpecComponent”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.
Exceptions
Section titled “Exceptions”SunSpecError
Section titled “SunSpecError”Raised when a device does not behave like a SunSpec device. Subclasses
Exception rather than ModbusError.
SunSpecMapShiftError
Section titled “SunSpecMapShiftError”SunSpecError subclass: a SunSpecComponent’s model header no longer matches
its discovered location, so the register map has changed. Rescan and rebuild
the components.
SunSpecGenerationError
Section titled “SunSpecGenerationError”Raised by the
generator
(modbus_connection.model.sunspec.generate) when emitting a static layout
would be incorrect.