indi_nexus.protocol¶
The single source of truth for the INDI 1.7 wire format: typed enums and
Pydantic models that serialize to both INDI XML (for indiserver) and JSON
(for browsers).
Enums¶
indi_nexus.protocol.enums ¶
INDI protocol enumerations.
Each enum subclasses :class:enum.StrEnum, so a member is the exact token used
on the INDI wire (e.g. IPState.OK == "Ok" is True) and Pydantic serialises
it to that token directly.
IPState ¶
Bases: _StrEnum
State of a vector property (the coloured status light in a GUI).
IPerm ¶
Bases: _StrEnum
Client access permission for a vector property.
ISRule ¶
Bases: _StrEnum
Constraint on how many switches in a switch vector may be On.
ISState ¶
Bases: _StrEnum
On/Off state of a single switch.
BLOBPolicy ¶
Bases: _StrEnum
How indiserver should deliver BLOBs to a client.
A client must send an enableBLOB with one of these policies before
indiserver will forward any BLOB; the default on the wire is Never.
Models¶
indi_nexus.protocol.models ¶
Typed Pydantic models for INDI properties and wire messages.
Design notes
-
A vector (
NumberVectoretc.) is the canonical, in-memory representation of a property. It carries the full metadata plus its elements. A driver holds vectors; a client caches them and applies incoming updates onto them. -
Element metadata (
format/min/max/stepon numbers,ruleon switch vectors, ...) is only present in adefmessage. Insetandnewmessages the wire carries justname+ value per element. We model that by making the metadata fields optional with defaults, so the same element class round-trips through both contexts. Clients are expected to mergesetvalues onto the previously-defined vector, which is standard INDI behavior. -
The
def/set/newdistinction is a wire intent, not a different data shape, so it is expressed by the thin event wrappers at the bottom of this module rather than by duplicating every vector five times.
Number ¶
Bases: _Element
A single numeric element (defNumber / oneNumber).
Text ¶
Bases: _Element
A single text element (defText / oneText).
Switch ¶
Bases: _Element
A single switch element (defSwitch / oneSwitch).
Light ¶
Bases: _Element
A single light element (defLight / oneLight). Read-only status.
BLOB ¶
Bases: _Element
A single BLOB element (defBLOB / oneBLOB).
data holds the decoded binary payload; the base64/size framing lives in
the codec, not the model.
NumberVector ¶
Bases: _Vector
A vector of numeric elements (defNumberVector / setNumberVector).
TextVector ¶
Bases: _Vector
A vector of text elements (defTextVector / setTextVector).
SwitchVector ¶
Bases: _Vector
A vector of switch elements with a selection rule.
selected ¶
selected() -> str | None
Return the name of the first element that is On, or None.
The idiomatic way to read a OneOfMany/AtMostOne client write:
such a write names the newly selected member (often only that
member), so the question is "which element is On in this request" -
never "what is element X", which raises when X was not sent.
Returns:
| Name | Type | Description |
|---|---|---|
name |
str or None
|
The first On element's name, or |
Source code in src/indi_nexus/protocol/models.py
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | |
LightVector ¶
Bases: _Vector
A vector of read-only light elements (no perm; lights are always RO).
BLOBVector ¶
Bases: _Vector
A vector of BLOB elements (binary payloads).
GetProperties ¶
Bases: _Model
Client -> device/server request to enumerate properties.
DelProperty ¶
Bases: _Model
Notification that a property (or a whole device) has gone away.
Message ¶
Bases: _Model
A free-form log/notification message.
EnableBLOB ¶
Bases: _Model
Client -> server request controlling BLOB delivery.
indiserver withholds BLOBs from a client until it sends this; the policy
applies to one device (and optionally one property when name is set).
DefVector ¶
Bases: _Model
A property definition (device -> client).
SetVector ¶
Bases: _Model
A value update to an already-defined property (device -> client).
NewVector ¶
Bases: _Model
A client's request to change a property's value (client -> device).
XML codec¶
indi_nexus.protocol.xml ¶
INDI 1.7 XML codec: models <-> canonical INDI XML.
Two directions:
- :func:
to_xmlserializes a model (DefVector/SetVector/NewVectoror a bare message) to the exactdef*/set*/new*XML thatindiserverand C++ INDI clients expect. - :class:
XMLStreamParserconsumes the raw byte stream from a socket or stdio pipe and yields fully-formed :data:~indi_nexus.protocol.models.IndiMessageobjects as complete top-level elements arrive, reassembling messages across arbitrary chunk boundaries (BLOB payloads included).
Number values honour the INDI printf-style format, including the %m
sexagesimal form used for RA/Dec, so values round-trip faithfully with libindi.
XMLStreamParser ¶
XMLStreamParser()
Incremental parser for the unbounded INDI element stream.
The INDI wire is a sequence of sibling top-level elements with no enclosing document root, so we feed a synthetic root and emit each depth-1 element as it completes, clearing consumed nodes to keep memory flat.
Start the pull parser and open a synthetic enclosing root element.
Source code in src/indi_nexus/protocol/xml.py
601 602 603 604 | |
feed ¶
feed(data: bytes | str) -> Iterator[IndiMessage]
Feed the next chunk of bytes and yield any completed messages.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
bytes or str
|
The next bytes or string from the stream. |
required |
Yields:
| Name | Type | Description |
|---|---|---|
message |
IndiMessage
|
Each top-level message that completed within this chunk. |
Source code in src/indi_nexus/protocol/xml.py
606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 | |
parse_number ¶
parse_number(text: str) -> float
Parse a number that may be decimal or sexagesimal.
Accepts plain decimals as well as dd:mm:ss (or space-separated)
sexagesimal forms used for RA/Dec, mirroring libindi's f_scansexa.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The raw element text. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
value |
float
|
The parsed value; |
Source code in src/indi_nexus/protocol/xml.py
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | |
format_number ¶
format_number(value: float, fmt: str) -> str
Format a number per an INDI printf-style format.
Handles ordinary printf conversions as well as the %m sexagesimal form
(e.g. %9.6m), field-width padded like libindi's fs_sexa.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
float
|
The number to format. |
required |
fmt
|
str
|
The INDI |
required |
Returns:
| Name | Type | Description |
|---|---|---|
text |
str
|
The formatted value. |
Source code in src/indi_nexus/protocol/xml.py
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | |
to_xml ¶
to_xml(msg: IndiMessage, *, pretty: bool = False) -> bytes
Serialise an INDI message model to canonical INDI XML bytes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
msg
|
IndiMessage
|
The message model to serialise. |
required |
pretty
|
bool
|
Whether to pretty-print the output. |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
xml |
bytes
|
The encoded XML. |
Source code in src/indi_nexus/protocol/xml.py
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 | |
message_from_xml ¶
message_from_xml(node: _Element) -> IndiMessage | None
Convert a single top-level INDI element node to a message model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
_Element
|
A top-level INDI element node. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
message |
IndiMessage or None
|
The parsed message, or |
Source code in src/indi_nexus/protocol/xml.py
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 | |
parse_indi ¶
parse_indi(data: bytes | str) -> list[IndiMessage]
Parse a complete chunk of INDI XML into message models.
Convenience wrapper over :class:XMLStreamParser for a self-contained chunk
that holds one or more complete top-level elements.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
bytes or str
|
The XML bytes or string to parse. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
messages |
list of IndiMessage
|
Every message found in the chunk, in order. |
Source code in src/indi_nexus/protocol/xml.py
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 | |
JSON codec¶
indi_nexus.protocol.json ¶
INDI JSON codec: models <-> typed JSON for browser clients.
The INDI wire toward indiserver is XML (see :mod:indi_nexus.protocol.xml);
toward browsers it is JSON. Both directions serialise the same Pydantic models,
so the JSON contract is just the models dumped to JSON - one source of truth, and
the frontend's TypeScript types can be generated from the model JSON schema.
:func:to_json and :func:from_json mirror to_xml / parse_indi. The
:data:~indi_nexus.protocol.models.IndiMessage union is discriminated by each
member's tag literal, so a single :class:pydantic.TypeAdapter round-trips
any message. BLOB payloads travel as base64 (configured on the base model).
to_json ¶
to_json(msg: IndiMessage) -> str
Serialise an INDI message model to a JSON string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
msg
|
IndiMessage
|
The message model to serialise. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
text |
str
|
The message as JSON (a |
Source code in src/indi_nexus/protocol/json.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 | |
from_json ¶
from_json(data: str | bytes) -> IndiMessage
Parse a JSON string into the matching INDI message model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
str or bytes
|
A single JSON object with a |
required |
Returns:
| Name | Type | Description |
|---|---|---|
message |
IndiMessage
|
The parsed, fully typed message model. |
Source code in src/indi_nexus/protocol/json.py
39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |