Device Support Matrix¶
This page documents the capability matrix — the curated table that
pylocal_akuvox.AkuvoxDevice.__aenter__() consults at connect time to
decide which operations are known-good, known-bad, or untested on the
attached device class.
Three CapabilityStatus values are used uniformly. SUPPORTED is
positive evidence (a maintainer or community reporter has executed the
operation against a real device and confirmed it works on the listed
firmware band). UNSUPPORTED is negative evidence (the device
returned an “unsupported action” envelope or “No handlers for this
request” for that operation). UNKNOWN is the absence of evidence —
the matrix maintainer has not classified the operation either way; the
per-call gate fails fast unless the integrator opts in with
device.attempt_unknown_capability = True.
For write-capable evidence collection and a redacted support artifact, see Capability Report API.
Public types¶
- class pylocal_akuvox.Capability[source]¶
Bases:
EnumCanonical capability identifiers.
String values use a
domain.action[.variant]shape so they are grep-friendly and stable in serialized notes/provenance.The enum is extensible: new members append. Existing members do not change name or value (FR-001).
- USER_LIST = 'user.list'¶
- USER_ADD = 'user.add'¶
- USER_MODIFY = 'user.modify'¶
- USER_DELETE = 'user.delete'¶
- SCHEDULE_LIST = 'schedule.list'¶
- SCHEDULE_ADD = 'schedule.add'¶
- SCHEDULE_MODIFY = 'schedule.modify'¶
- SCHEDULE_DELETE = 'schedule.delete'¶
- GROUP_LIST = 'group.list'¶
- GROUP_ADD = 'group.add'¶
- GROUP_MODIFY = 'group.modify'¶
- GROUP_DELETE = 'group.delete'¶
- CONTACT_LIST = 'contact.list'¶
- CONTACT_ADD = 'contact.add'¶
- CONTACT_MODIFY = 'contact.modify'¶
- CONTACT_DELETE = 'contact.delete'¶
- RELAY_TRIGGER_API = 'relay.trigger.api'¶
- RELAY_TRIGGER_FCGI = 'relay.trigger.fcgi'¶
- RELAY_STATUS = 'relay.status'¶
- DEVICE_CONFIG_GET = 'device.config.get'¶
- DEVICE_CONFIG_SET = 'device.config.set'¶
- LOG_DOOR = 'log.door'¶
- LOG_CALL = 'log.call'¶
- KEY_DISCOVERY = 'key.discovery'¶
- class pylocal_akuvox.CapabilityStatus[source]¶
Bases:
EnumThree-valued capability status.
SUPPORTED— confirmed positive evidence.UNSUPPORTED— confirmed negative evidence (e.g.unsupported actionenvelope orNo handlers for this request).UNKNOWN— no positive evidence either way; the conservative default returned byDeviceCapabilities.status_of()for any capability not explicitly listed.
- SUPPORTED = 'supported'¶
- UNSUPPORTED = 'unsupported'¶
- UNKNOWN = 'unknown'¶
- class pylocal_akuvox.DeviceCapabilities[source]¶
Bases:
objectEffective capability profile carried by one
AkuvoxDevice.All four mapping fields (
capabilities,field_aliases,schema_shapes,notes) are wrapped intypes.MappingProxyTypeby__post_init__(), so post-construction mutation raisesTypeError. Constructors accept plaindictfor ergonomic call-sites; the read-only wrapping is applied transparently. This enforces the deep immutability invariant gating logic relies on (a caller cannot dodevice._capabilities.notes["evil"] = "x"to corrupt the profile). See testtest_device_capabilities_is_deeply_immutable.- capabilities: Mapping[Capability, CapabilityStatus]¶
- status_of(capability)[source]¶
Return the status for
capability, defaulting to UNKNOWN.If
capability not in self.capabilities, returnsCapabilityStatus.UNKNOWN(the “absent → UNKNOWN” default). This is the canonical representation: write capabilities the probe did not classify are absent fromcapabilities, not present-with-UNKNOWN.status_ofcollapses both shapes into the same observable behaviour so callers never need to distinguish.- Return type:
- Parameters:
capability (Capability)
- require(capability, *, allow_unknown=False)[source]¶
Raise
AkuvoxUnsupportedErrorunless gated SUPPORTED.With
allow_unknown=True,UNKNOWNstatus falls through (does not raise); the runtime HTTP attempt then either succeeds or surfacesAkuvoxUnsupportedErrorfrom the envelope-level translation in_http.py.UNSUPPORTEDalways raises regardless of theallow_unknownflag.Per
contracts/unsupported-error.md§”Raise-site contract” this raise populates the structured fieldscapability,device_class, andreasonon the resulting exception so the integrator (and the closed-settest_reason_taxonomy_closedaudit) can observe which capability gate fired and which three-valued status fired it.- Return type:
- Parameters:
capability (Capability)
allow_unknown (bool)
- property supported_set: frozenset[Capability]¶
Return the set of capabilities whose status is SUPPORTED.
- __init__(*, device_class, firmware_version, capabilities, field_aliases, schema_shapes, notes=<factory>, provenance=None)¶
Matrix entries (live)¶
The table below is rendered at documentation build time directly from
pylocal_akuvox.capability_matrix.CAPABILITY_MATRIX. If the matrix
constant changes, the table updates automatically on the next build.
The per-class sections that follow give the supplied evidence for each
(model prefix, firmware band) pair documented here.
Model prefix |
Firmware band |
Supported |
Unsupported |
Unknown |
Provenance |
|---|---|---|---|---|---|
IT83 |
83.30.10.4 |
2 |
2 |
20 |
community-reporter (issue #130 / #122) @ lib 1.6.0, observed 2026-06-13 |
X915S |
2915.30.10.114+ |
20 |
3 |
1 |
maintainer’s bench unit @ lib 1.6.0, observed 2026-06-13 |
E18C |
18.30.11.* |
23 |
0 |
1 |
maintainer’s bench unit @ lib 1.6.0, observed 2026-06-13 |
E18 |
18.30.10.118+ |
23 |
0 |
1 |
maintainer’s bench unit @ lib 1.6.0, observed 2026-06-13 |
A08S |
108.30.10.144+ |
14 |
9 |
1 |
maintainer’s bench unit @ lib 1.6.0, observed 2026-06-13 |
R20K |
320.30.3.122+ |
22 |
0 |
2 |
community-reporter (issue #229) @ lib 1.6.0, observed 2026-06-13 |
R20A |
320.30.11.63+ |
22 |
1 |
1 |
community reporter (issue #234) @ lib 1.6.0, observed 2026-06-13 |
X916 |
916.30.10.* |
23 |
0 |
1 |
maintainer’s bench unit @ lib 1.6.0, observed 2026-06-13 |
Device classes¶
X916¶
Door-phone reference baseline. Maintainer’s bench unit. All read /
write CRUD operations on users, schedules, groups, and contacts are
SUPPORTED; relay trigger uses the API variant
(Capability.RELAY_TRIGGER_API); ScheduleRelay and
Schedule-Relay are both accepted on read and emitted on write
(the dual-write contract).
X915S¶
Door-phone variant on the X915S product line. Reads the bare
Schedule key in user payloads, so the
matrix entry lists Schedule first in the
schedule_relay field-alias read order. add_contact,
modify_contact, and delete_contact are all UNSUPPORTED
on this variant: apartment-book contact records are read-only over
the HTTP API, and the device returns an “unsupported action”
envelope for contact-write attempts. Contact payloads use the
apartment-book schema shape.
E18C¶
Door-phone variant. Same capability set as X916 modulo firmware band; aliasing matches X916 byte-for-byte.
E18¶
Door-phone variant with firmware floor 18.30.10.118+. Same
capability set as E18C and X916: user, schedule, group, and contact
CRUD operations are SUPPORTED alongside relay status, API relay
trigger, device config get / set, door and call logs, and key
discovery. Contact payloads use the door-phone schema shape, and
ScheduleRelay / Schedule-Relay aliasing matches E18C and X916.
A08S¶
Access-unit class with firmware floor 108.30.10.144+. User and
schedule CRUD operations are SUPPORTED alongside relay status, API
relay trigger, device config get / set, door logs, and key discovery.
The FCGI relay trigger remains UNKNOWN. Contacts, groups, and call
logs are UNSUPPORTED on this class, so the matrix leaves contact
schema-shape parsing unset.
R20K¶
Door-phone class with firmware floor 320.30.3.122+. Community
write-capable reports confirmed full user, schedule, and contact CRUD;
group list / add / delete; door-log and call-log reads; relay status;
key discovery; API relay trigger; and device config get as
SUPPORTED. Device config set is also SUPPORTED, inferred from
R20-family equivalence with R20A’s directly confirmed webhook config
writes rather than a direct R20K report. Contact payloads use the
door-phone schema shape, and ScheduleRelay / Schedule-Relay
aliasing follows the standard door-phone profile. Group modify and the
FCGI relay trigger remain UNKNOWN pending further testing.
R20A¶
R20-family door-phone class with firmware floor 320.30.11.63+. A
community write-capable report confirmed full user, schedule, and
contact CRUD; group add / delete; API relay trigger; device config get /
set; and all read operations as SUPPORTED. Contact payloads use the
door-phone schema shape, and ScheduleRelay / Schedule-Relay
aliasing follows the standard door-phone profile. The legacy FCGI
OpenDoor path (Capability.RELAY_TRIGGER_FCGI) is UNSUPPORTED on
this firmware: the device returns “please use new interface”, so callers
should use the standard API relay trigger. Group modify remains
UNKNOWN pending testing.
IT83¶
Indoor-monitor product line (community reporter). The /api/relay/*
endpoints return “No handlers for this request” on this device class
(Capability.RELAY_TRIGGER_API and Capability.RELAY_STATUS
are both UNSUPPORTED); the /fcgi/do?action=OpenDoor variant
(Capability.RELAY_TRIGGER_FCGI) works. All user / contact /
schedule / group operations remain UNKNOWN — the community
reporter did not exercise them.
Contributing a new device class¶
Adding support for a new firmware band whose variation is limited to
the known axes (endpoint availability, field-name aliasing, schema
shape, action availability) requires a single-file edit to
pylocal_akuvox.capability_matrix. The recipe below is the
worked walkthrough referenced from
specs/008-capability-matrix/contracts/matrix-lookup.md
§”Adding a new entry”:
Add a new
(DeviceClassPattern(...), DeviceCapabilities(...))tuple toCAPABILITY_MATRIXin the most-specific-first position.DeviceClassPatternaccepts amodel_prefix(matched withstr.startswith()against the device’s reported model) and afirmware_bandin one of three forms — exact ("83.30.10.4"), glob ("916.30.10.*"), or floor ("2915.30.10.114+").Populate the entry’s
capabilitiesmapping with the per-capabilitypylocal_akuvox.CapabilityStatusvalues for which there is positive evidence. Capabilities for which there is no positive evidence either way may be omitted from the mapping; they default toUNKNOWN. Maintainers are encouraged to omit rather than guess.Record confirmed-negative evidence (e.g. an “unsupported action” envelope was observed for the operation) as
UNSUPPORTEDso the per-call gate fails fast instead of suggesting the integrator opt in.If the entry uses field aliases or schema shapes that already have matrix-language coverage (e.g. another existing entry has
pylocal_akuvox.SchemaShape.APARTMENT_BOOK), no further code change is required.If the entry uses a brand-new schema shape (e.g. a different contact-payload structure) add the new variant to
pylocal_akuvox.SchemaShapeand to the corresponding parser’s dispatch — this is not a large refactor; it is a one-line addition to the central enum plus a parser branch. Field-alias keys, by contrast, are plain strings stored inDeviceCapabilities.field_aliasesand do not require an enum addition; a new alias can be introduced by listing it in the entry alone. Newpylocal_akuvox.Capabilitymembers should be reserved for genuinely new operations or endpoints, not for alias variants of existing ones.Add a new test case in
tests/unit/test_matrix.pyasserting the entry’s provenance and selected capability deltas.Update this page (
docs/api/capabilities.rst) to mention the new device class. The consistency test intests/unit/test_docs_matrix_consistency.pywill fail CI if the page omits a matrix-registered prefix or mentions an unknown prefix in a heading.
The same recipe is exercised programmatically by
tests/unit/test_matrix.py::test_add_hypothetical_entry (see
specs/008-capability-matrix/quickstart.md step 9).