registry

package
v0.0.0-...-e0a1550 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: AGPL-3.0 Imports: 11 Imported by: 0

Documentation

Index

Constants

View Source
const ServicePlane = "Service"

Variables

View Source
var (
	ErrPortalDuplicatePlane = errors.New("portal: duplicate plane")
	ErrPortalDuplicateNode  = errors.New("portal: duplicate node")
	ErrPortalDuplicateEdge  = errors.New("portal: duplicate edge")
	ErrPortalInvalidEntry   = errors.New("portal: invalid entry")
)
View Source
var (
	ErrProjectionInvalidPlane   = errors.New("projection: invalid plane")
	ErrProjectionInvalidSegment = errors.New("projection: invalid path segment")
	ErrProjectionInvalidPath    = errors.New("projection: invalid path")
	ErrProjectionInvalidNode    = errors.New("projection: invalid node")
	ErrProjectionInvalidEdge    = errors.New("projection: invalid edge")
)
View Source
var ErrScanDirectedEmptyTargets = errors.New("scan: directed scan requires explicit non-empty targets")

ErrScanDirectedEmptyTargets is returned by ScanDirected when it is invoked with a nil or empty targets slice. ScanDirected requires an explicit, bounded target list; the historical full-range fall-through that Scan performs when targets is nil is deliberately not available via this API.

See AD10 in helianthus-execution-plans/startup-admission-discovery-w17-26.locked/ 12-decision-matrix.md for the directed-scan contract.

Functions

func DefaultScanTargets

func DefaultScanTargets() []byte

DefaultScanTargets returns the default address range for scanning.

func MethodRoutableOf

func MethodRoutableOf(method Method) bool

MethodRoutableOf resolves routability with legacy default=true behavior.

func ReadVaillantScanID

func ReadVaillantScanID(ctx context.Context, bus ScanBus, source byte, target byte) (string, bool, error)

ReadVaillantScanID sends B5.09 identity reads (chunks 0x24..0x27) and returns the formatted Vaillant serial number. It is the exported twin of the internal readVaillantScanID so that callers outside this package (e.g. the gateway's ebusd-tcp preload enrichment) can invoke it.

Types

type AddressSlot

type AddressSlot struct {
	Addr              byte
	Role              SlotRole
	DiscoverySource   DiscoverySource
	VerificationState VerificationState
	Device            *deviceEntry
	FirstObservedAt   time.Time
	LastObservedAt    time.Time
}

func (*AddressSlot) AddressByRole

func (s *AddressSlot) AddressByRole(role SlotRole) (byte, bool)

AddressByRole forwards to the wrapped Device's AddressByRole; falls back to s.Addr matching when Device is nil and the slot's own Role matches the requested role.

func (*AddressSlot) Addresses

func (s *AddressSlot) Addresses() []byte

func (*AddressSlot) DeviceID

func (s *AddressSlot) DeviceID() string

func (*AddressSlot) HardwareVersion

func (s *AddressSlot) HardwareVersion() string

func (*AddressSlot) MacAddress

func (s *AddressSlot) MacAddress() string

func (*AddressSlot) Manufacturer

func (s *AddressSlot) Manufacturer() string

func (*AddressSlot) Planes

func (s *AddressSlot) Planes() []Plane

func (*AddressSlot) PrimaryDisplayAddress

func (s *AddressSlot) PrimaryDisplayAddress() byte

PrimaryDisplayAddress returns the slot's display address — the wrapped Device's PrimaryDisplayAddress when present, otherwise the slot's own Addr. Phase C M-C6c: replaces the removed AddressSlot.Address() method (which had identical semantics but conflated display vs. routing intent).

func (*AddressSlot) Projections

func (s *AddressSlot) Projections() []Projection

func (*AddressSlot) SerialNumber

func (s *AddressSlot) SerialNumber() string

func (*AddressSlot) SoftwareVersion

func (s *AddressSlot) SoftwareVersion() string

type AddressSlotSnapshot

type AddressSlotSnapshot struct {
	Addr              byte
	Role              SlotRole
	DiscoverySource   DiscoverySource
	VerificationState VerificationState
	FirstObservedAt   time.Time
	LastObservedAt    time.Time
	DeviceAttached    bool
}

AddressSlotSnapshot is a value-typed copy of an AddressSlot's observable fields. Snapshots are taken under r.mu.RLock so callers can read the fields without holding any registry lock and without risking torn reads from concurrent writers.

P8.1 — addresses the lock-free read advisory raised by the GitHub Codex bot on helianthus-ebusgateway PR #589: the gateway's AddressTable previously dereferenced a live AddressSlot pointer (returned by LookupSlot) outside the registry's RLock to read DiscoverySource / VerificationState. AddressSlotSnapshot eliminates that race surface — the value copy is immune to concurrent mutations because the writer must acquire r.mu.Lock() (which blocks behind the RLock taken in LookupSlotSnapshot below) before changing the underlying slot.

Note: Device is reduced to a boolean (DeviceAttached) because returning the *deviceEntry pointer would re-introduce lock-free dereferencing of mutable identity fields downstream. Callers that need the entry's identity fields can fetch the entry via Lookup, but should be aware that the returned DeviceEntry interface still reads through to the registry's internal entry struct — those reads are not snapshot-isolated. A future entry-snapshot API may be added if the same pattern proves desirable for identity reads.

type BusFace

type BusFace struct {
	Addr              byte
	Role              SlotRole
	DiscoverySource   DiscoverySource
	VerificationState VerificationState
	AccessProtocols   []string
}

type CanonicalIndex

type CanonicalIndex struct {
	// contains filtered or unexported fields
}

func BuildCanonicalIndex

func BuildCanonicalIndex(projections []Projection) (CanonicalIndex, error)

func CanonicalIndexForEntry

func CanonicalIndexForEntry(entry DeviceEntry) (CanonicalIndex, error)

func (CanonicalIndex) Canonical

func (index CanonicalIndex) Canonical(id NodeID) (ProjectionPath, bool)

func (CanonicalIndex) PlanePath

func (index CanonicalIndex) PlanePath(plane string, id NodeID) (ProjectionPath, bool)

func (CanonicalIndex) PlanePaths

func (index CanonicalIndex) PlanePaths(id NodeID) map[string]ProjectionPath

type DeviceEntry

type DeviceEntry interface {
	// AddressByRole returns the first BusFace address whose Role
	// matches the requested SlotRole. Returns (0, false) when no face
	// matches. Used by routing code to address the correct byte for
	// the intended frame type (per AddressClass taxonomy).
	//
	// Phase C M-C6c: replaces the previous ambiguous Address() method,
	// which conflated the "show me a representative byte" use case
	// (now PrimaryDisplayAddress) with the "give me the routing-
	// correct byte for this frame type" use case (this method). The
	// removed method silently returned the initiator byte for an
	// aliased canonical pair (e.g. BAI 0x03↔0x08), causing M2S writes
	// to mis-route to the initiator side. AddressByRole forces
	// callers to declare their intent.
	AddressByRole(role SlotRole) (byte, bool)
	// PrimaryDisplayAddress returns a representative address for log /
	// UI display. May be initiator OR target for aliased pairs; do
	// NOT use for wire routing. For routing, use AddressByRole.
	PrimaryDisplayAddress() byte
	Addresses() []byte
	Manufacturer() string
	DeviceID() string
	SerialNumber() string
	MacAddress() string
	SoftwareVersion() string
	HardwareVersion() string
	Planes() []Plane
	Projections() []Projection
}

func Scan

func Scan(ctx context.Context, bus ScanBus, registry *DeviceRegistry, source byte, targets []byte) ([]DeviceEntry, error)

Scan performs a 07 04 identification scan over the provided targets.

func ScanDirected

func ScanDirected(ctx context.Context, bus ScanBus, registry *DeviceRegistry, source byte, targets []byte) ([]DeviceEntry, error)

ScanDirected performs a 07 04 identification scan over an explicit, bounded set of targets. Unlike Scan, it refuses to fall through to DefaultScanTargets: callers MUST supply a non-empty targets slice. Nil or empty input returns ErrScanDirectedEmptyTargets and the bus is not touched.

Behaviour when targets is non-empty is identical to Scan. The same target-capability rules apply (FrameTypeForTarget, initiator-capable exclusion, SYN/ESC via FrameTypeUnknown), dedupe is consistent, and the collision-retry loop is shared.

This API is intended for startup admission on non-ebusd-tcp direct transports where the gateway MUST probe only promoted suspects — never the full 0x01..0xFD address range. Callers on the ebusd-tcp sanctioned bounded-retry path should continue to use Scan directly so that Scan(ctx, bus, registry, source, nil) retains its fall-through semantics.

See helianthus-execution-plans/startup-admission-discovery-w17-26.locked/ 12-decision-matrix.md#ad10 for the directed-scan contract.

type DeviceEntrySnapshot

type DeviceEntrySnapshot struct {
	PrimaryAddress  byte
	Addresses       []byte
	Faces           []BusFace
	Manufacturer    string
	DeviceID        string
	SerialNumber    string
	MacAddress      string
	SoftwareVersion string
	HardwareVersion string
	// Planes + Projections — slice headers captured under RLock at
	// snapshot time. The interface elements (Plane, Method, etc.) are
	// immutable after PlaneProvider.CreatePlanes returns; safe to
	// read after the lock has been released.
	Planes      []Plane
	Projections []Projection
}

DeviceEntrySnapshot is a value-typed copy of a DeviceEntry's observable identity fields. Snapshots are taken under r.mu.RLock so callers can read the fields without holding any registry lock and without risking torn reads from concurrent writers (Register / RegisterStaticSeed / RegisterPassiveObserved / AliasAddresses / detachAddressLocked).

P9 — addresses Codex post-P8.3 audit: the DeviceEntry interface methods (Manufacturer / DeviceID / SerialNumber / etc.) read `d.info.<Field>` lock-free. Concurrent Register replaces `entry.info` with a new DeviceInfo struct (line 240 `entry.info = storedInfo`); a reader holding the *deviceEntry pointer can observe a torn read of the string fields (string is a 16-byte ptr+len header).

DeviceEntrySnapshot copies all string and slice fields under RLock; the snapshot is disconnected from registry storage and safe to read concurrently. Slice copies (Addresses, Faces) prevent callers from mutating registry state through the snapshot.

SCOPE (P9.x): Planes and Projections were originally omitted on the theory that their interface trees transitively exposed registry- mutable state. In practice the *plane and *method implementations (vaillant providers + similar) are constructed once in PlaneProvider.CreatePlanes / ProjectionProvider.CreateProjections and never mutated afterward; the only registry-side write to the `entry.planes` / `entry.projections` slice headers happens during the identity-merge path (mergeEntries dst.planes = src.planes / dst.projections = src.projections). Capturing those slice headers under RLock therefore produces a stable view: readers iterating the snapshot's Planes / Projections see element references that remain valid for the lifetime of the snapshot, with no risk of a mid-iteration slice reassignment.

P9.x adds Planes + Projections to DeviceEntrySnapshot so the graphql.BuildSchema hot path can drop its live-pointer Iterate usage. Callers that mutate the registry (registerLocked path) must continue to use the live `*deviceEntry` pointer; the snapshot is READ-ONLY by construction.

func (DeviceEntrySnapshot) AddressByRole

func (s DeviceEntrySnapshot) AddressByRole(role SlotRole) (byte, bool)

AddressByRole mirrors deviceEntry.AddressByRole using the snapshot's Faces slice. Returns (0, false) when no face matches the requested role (after the same role-class fallback rules used by the live implementation).

func (DeviceEntrySnapshot) PrimaryDisplayAddress

func (s DeviceEntrySnapshot) PrimaryDisplayAddress() byte

PrimaryDisplayAddress mirrors deviceEntry.PrimaryDisplayAddress for callers that work with the value-typed snapshot.

type DeviceInfo

type DeviceInfo struct {
	Address         byte
	Manufacturer    string
	DeviceID        string
	SerialNumber    string
	MacAddress      string
	SoftwareVersion string
	HardwareVersion string
}

type DeviceRegistry

type DeviceRegistry struct {
	// contains filtered or unexported fields
}

func NewDeviceRegistry

func NewDeviceRegistry(providers []PlaneProvider) *DeviceRegistry

func (*DeviceRegistry) AliasAddresses

func (r *DeviceRegistry) AliasAddresses(a, b byte) error

Lookup returns the canonical *DeviceEntry for the given address, or (nil, false) if no device occupies that slot. Preserved (signature unchanged) so existing callers continue to compile.

func (*DeviceRegistry) Iterate

func (r *DeviceRegistry) Iterate(fn func(DeviceEntry) bool)

func (*DeviceRegistry) IterateSnapshots

func (r *DeviceRegistry) IterateSnapshots(fn func(DeviceEntrySnapshot) bool)

IterateSnapshots visits each registered device entry as a value-typed snapshot. Snapshots are built under r.mu.RLock; the lock is released BEFORE the callback runs. Callers can therefore safely call any DeviceRegistry method from within the callback (no deadlock risk).

This matches the existing Iterate API's lock-then-snapshot-then- unlock contract. The only behavioural difference vs Iterate is the value-typed snapshot vs live entry pointer (Codex P9 review pass 1 MINOR FINDING_2).

P9 — Iterate remains for callers that need live entry pointers (or Planes / Projections, which the snapshot intentionally omits). New consumers SHOULD prefer IterateSnapshots.

func (*DeviceRegistry) Lookup

func (r *DeviceRegistry) Lookup(address byte) (DeviceEntry, bool)

func (*DeviceRegistry) LookupEntrySnapshot

func (r *DeviceRegistry) LookupEntrySnapshot(address byte) (DeviceEntrySnapshot, bool)

LookupEntrySnapshot returns a value-typed snapshot of the DeviceEntry registered for addr, taken under r.mu.RLock. The race-free counterpart to Lookup for callers that only need the entry's observable identity fields (gateway MCP / GraphQL / observability projections). Returns (zero, false) when no entry exists for the address.

P9 — Lookup remains for callers that need the live entry pointer (e.g. registry-internal mutation paths or callers that need Planes/Projections).

func (*DeviceRegistry) LookupSlot

func (r *DeviceRegistry) LookupSlot(address byte) (*AddressSlot, bool)

LookupSlot returns the AddressSlot for the requested address (M1 address-table accessor), with the slot's own role/source/confidence metadata. When the address is aliased to a multi-address device, the returned slot.Device pointer is shared with the primary slot, but slot.Addr/Role/DiscoverySource/VerificationState describe the REQUESTED address — callers inspecting per-address metadata get the per-slot view (Codex P2: return-the-requested-address-slot).

func (*DeviceRegistry) LookupSlotSnapshot

func (r *DeviceRegistry) LookupSlotSnapshot(address byte) (AddressSlotSnapshot, bool)

LookupSlotSnapshot returns a value-typed snapshot of the AddressSlot at addr, taken under r.mu.RLock. Callers can read the snapshot fields without any registry-lock concerns. Returns (zero, false) when no slot exists for the address.

P8.1 — the race-free counterpart to LookupSlot for callers that only need the slot's observable fields (gateway address-table projection, MCP/GraphQL surfaces). LookupSlot remains for callers that need the live pointer (e.g. registry-internal mutation paths holding the appropriate lock externally).

func (*DeviceRegistry) MarkSlotPassiveObserved

func (r *DeviceRegistry) MarkSlotPassiveObserved(address byte, role SlotRole, observedAt time.Time)

MarkSlotPassiveObserved updates an AddressSlot for an address that was passively observed by the gateway (e.g. by AddressTableInserter on positive ACK following a complete request). Writes Role / Discovery Source / VerificationState / FirstObservedAt / LastObservedAt under the registry write lock so concurrent readers via LookupSlot / Lookup do not see torn state.

This API replaces direct *AddressSlot field mutation by the gateway inserter, which was racy with other readers (Codex P2 follow-up from PR #565). Idempotent: re-marking the same slot only advances DiscoverySource / VerificationState monotonically (the slot retains the higher of the existing and new value, matching observeAddressSlotLocked's monotonic semantics).

SCOPE: this API only mutates the AddressSlot. It does NOT attach the slot to a device entry. To plant a NEW passively-observed address with identity attached AND label it correctly in a single critical section, use RegisterPassiveObserved (which composes registerLocked + this primitive). Calling Register followed by MarkSlotPassiveObserved produces a label-misorder because Register stamps DiscoverySourceActiveConfirmed and the monotonic guard then refuses to downgrade — that was the P8 bug fixed in RegisterPassiveObserved.

func (*DeviceRegistry) MarkSlotStaticSeed

func (r *DeviceRegistry) MarkSlotStaticSeed(address byte, role SlotRole, seededAt time.Time)

MarkSlotStaticSeed updates an AddressSlot for an address known from a product taxonomy seed table, mirroring MarkSlotPassiveObserved (lock discipline, monotonic upgrade semantics, idempotence) but stamping DiscoverySourceStaticSeed / VerificationStateCandidate.

SCOPE: this API only mutates the AddressSlot. It does NOT attach the slot to a device entry; if `slot.Device` is nil at call time it stays nil, the address is NOT added to `r.entries`, and the device's `addresses` / `Faces` lists are NOT updated. Therefore:

  • To plant a NEW seeded address with identity attached, use RegisterStaticSeed (which composes registerLocked + this stamp). Each face that should appear in `Lookup` / `AddressByRole` needs its own RegisterStaticSeed call; the identity-merge in registerLocked joins them when DeviceInfo identity matches, or they can be aliased post-hoc via AliasAddresses.

  • The use case for MarkSlotStaticSeed in isolation is updating an AddressSlot that was already attached to a device by some prior path (Register / RegisterStaticSeed / AliasAddresses) to upgrade its discovery_source / verification labels — for example, marking a slot newly populated by passive observation as "now also seeded from the static table" so the operator surface reflects that the addresses are pre-known.

Idempotent. Re-calling on a slot already at higher DiscoverySource (e.g. ActiveConfirmed) is a no-op for the discovery label, though it may still upgrade VerificationState if the existing state is below Candidate.

func (*DeviceRegistry) Register

func (r *DeviceRegistry) Register(info DeviceInfo) DeviceEntry

func (*DeviceRegistry) RegisterPassiveObserved

func (r *DeviceRegistry) RegisterPassiveObserved(info DeviceInfo, role SlotRole, observedAt time.Time) DeviceEntry

RegisterPassiveObserved plants identity for an address newly observed on the wire by the gateway's passive inserter. Mirrors Register's identity-merge behaviour but stamps the AddressSlot with DiscoverySourcePassiveObserved / VerificationStateCorroborated so the observability surface (`/metrics`, MCP `bus.summary.get`, address-table snapshots) correctly shows the slot's provenance as passive observation rather than active confirmation.

P8 fix: previously the gateway inserter called Register (which stamps ActiveConfirmed/IdentityConfirmed) followed by MarkSlotPassiveObserved. The monotonic ladder (PassiveObserved < ActiveConfirmed) made the second call a no-op, so passively-observed slots were misreported as `active_confirmed`. RegisterPassiveObserved performs the identity-merge AND the passive-label stamping atomically under a single lock acquisition, avoiding the misorder.

Subsequent label progression (after RegisterPassiveObserved):

  • An active confirmation (e.g. directed scan) DOES advance the DiscoverySource to ActiveConfirmed (PassiveObserved < ActiveConfirmed) AND VerificationState to IdentityConfirmed.
  • A static-seed mark on a passively-observed slot DOES advance DiscoverySource to StaticSeed (PassiveObserved < StaticSeed) — pre-known taxonomy outranks wire-only inference.

Single lock acquisition — composes registerLocked, then the shared passive-observation primitive, then syncEntryFacesLocked.

func (*DeviceRegistry) RegisterProvider

func (r *DeviceRegistry) RegisterProvider(provider PlaneProvider)

func (*DeviceRegistry) RegisterStaticSeed

func (r *DeviceRegistry) RegisterStaticSeed(info DeviceInfo, role SlotRole, seededAt time.Time) DeviceEntry

RegisterStaticSeed plants identity for an address known from a product taxonomy table BEFORE any wire traffic has been observed. Mirrors Register's identity-merge behavior but stamps the AddressSlot with DiscoverySourceStaticSeed / VerificationStateCandidate so the observability surface (`/metrics`, MCP `bus.summary.get`, address-table snapshots) correctly shows the slot's provenance as a pre-known seed rather than active confirmation.

On a clean cold boot a static-seeded slot subsequently observed passively will: NOT advance DiscoverySource (PassiveObserved < StaticSeed in the monotonic enum order), WILL advance VerificationState from Candidate to Corroborated. An active confirmation (e.g. directed scan) DOES advance DiscoverySource to ActiveConfirmed (StaticSeed < ActiveConfirmed) AND VerificationState to IdentityConfirmed.

Single lock acquisition — composes registerLocked, then the shared static-seed stamping primitive, then syncEntryFacesLocked.

func (*DeviceRegistry) WithObservationGeneration

func (r *DeviceRegistry) WithObservationGeneration(fn func(uint64)) bool

WithObservationGeneration executes fn while holding the registry read lock and supplies the current monotonic observation generation. Public registry mutations advance the generation under the corresponding write lock, so a writer linearizes either before the callback begins or after it returns.

fn must remain bounded and must not call another DeviceRegistry method: the callback deliberately executes inside the read critical section so callers can atomically compare a captured generation and commit derived state before a relevant registry mutation can interleave.

The method returns false without invoking fn when either receiver or callback is nil.

type DiscoverySource

type DiscoverySource int
const (
	DiscoverySourceUnknown DiscoverySource = iota
	DiscoverySourcePassiveObserved
	DiscoverySourceStaticSeed
	DiscoverySourceActiveConfirmed
)

type Edge

type Edge struct {
	ID   EdgeID
	From NodeID
	To   NodeID
}

func NewEdge

func NewEdge(plane string, from NodeID, to NodeID) (Edge, error)

type EdgeID

type EdgeID string

func StableEdgeID

func StableEdgeID(plane string, from NodeID, to NodeID) (EdgeID, error)

type EntryIterator

type EntryIterator interface {
	Iterate(func(DeviceEntry) bool)
}

EntryIterator is the minimal registry surface required by projection helpers.

type FrameTemplate

type FrameTemplate interface {
	Primary() byte
	Secondary() byte
}

type Method

type Method interface {
	Name() string
	ReadOnly() bool
	Template() FrameTemplate
	ResponseSchema() schema.SchemaSelector
}

type MethodDanger

type MethodDanger string

MethodDanger describes risk level for invoke safety controls.

const (
	MethodDangerUnknown   MethodDanger = "unknown"
	MethodDangerSafe      MethodDanger = "safe"
	MethodDangerDangerous MethodDanger = "dangerous"
)

func MethodDangerOf

func MethodDangerOf(method Method) MethodDanger

MethodDangerOf resolves danger with mutability-based fallback.

type MethodDangerProvider

type MethodDangerProvider interface {
	Danger() MethodDanger
}

MethodDangerProvider optionally overrides default danger inference.

type MethodMetadata

type MethodMetadata struct {
	Mutability MethodMutability
	Danger     MethodDanger
	Routable   bool
}

MethodMetadata is the normalized method safety/routing contract.

Backward compatibility defaults: - Mutability: derived from Method.ReadOnly() when not explicitly provided. - Danger: safe for read_only, dangerous for mutating/unknown. - Routable: true when not explicitly provided.

func ResolveMethodMetadata

func ResolveMethodMetadata(method Method) MethodMetadata

ResolveMethodMetadata returns normalized metadata for a method with backward-compatible defaults for legacy Method implementations.

type MethodMutability

type MethodMutability string

MethodMutability describes whether a method has side effects.

const (
	MethodMutabilityUnknown  MethodMutability = "unknown"
	MethodMutabilityReadOnly MethodMutability = "read_only"
	MethodMutabilityMutating MethodMutability = "mutating"
)

func MethodMutabilityOf

func MethodMutabilityOf(method Method) MethodMutability

MethodMutabilityOf resolves mutability with legacy ReadOnly fallback.

type MethodMutabilityProvider

type MethodMutabilityProvider interface {
	Mutability() MethodMutability
}

MethodMutabilityProvider optionally overrides default mutability inference.

type MethodRoutableProvider

type MethodRoutableProvider interface {
	Routable() bool
}

MethodRoutableProvider optionally overrides default routability.

type Node

type Node struct {
	ID            NodeID
	Path          ProjectionPath
	CanonicalPath ProjectionPath
}

func NewNode

func NewNode(path ProjectionPath, canonical ProjectionPath) (Node, error)

type NodeID

type NodeID string

func StableNodeID

func StableNodeID(canonical ProjectionPath) (NodeID, error)

type PathSegment

type PathSegment struct {
	Name     string
	Location bool
}

func (PathSegment) String

func (segment PathSegment) String() string

type Plane

type Plane interface {
	Name() string
	Methods() []Method
}

type PlaneIndex

type PlaneIndex struct {
	// contains filtered or unexported fields
}

PlaneIndex provides indexed access to nodes and edges within a projection plane.

Example:

plane, ok := portal.PlaneIndex("Observability")
if ok {
    node, ok, err := plane.NodeByCanonical(ProjectionPath{
        Plane: ServicePlane,
        Segments: []PathSegment{
            {Name: "devices"},
            {Name: "boiler"},
        },
    })
    _ = node
    _ = err
}

func NewPlaneIndex

func NewPlaneIndex(projection Projection) (PlaneIndex, error)

NewPlaneIndex builds an index from a single projection.

func (*PlaneIndex) EdgeByID

func (index *PlaneIndex) EdgeByID(id EdgeID) (Edge, bool)

EdgeByID looks up an edge by its stable ID.

func (*PlaneIndex) EdgeCount

func (index *PlaneIndex) EdgeCount() int

EdgeCount returns the number of edges indexed for the plane.

func (*PlaneIndex) EdgesFrom

func (index *PlaneIndex) EdgesFrom(id NodeID) []Edge

EdgesFrom returns edges originating from the provided node ID.

func (*PlaneIndex) EdgesTo

func (index *PlaneIndex) EdgesTo(id NodeID) []Edge

EdgesTo returns edges targeting the provided node ID.

func (*PlaneIndex) NodeByCanonical

func (index *PlaneIndex) NodeByCanonical(canonical ProjectionPath) (Node, bool, error)

NodeByCanonical looks up a node by canonical Service path.

func (*PlaneIndex) NodeByID

func (index *PlaneIndex) NodeByID(id NodeID) (Node, bool)

NodeByID looks up a node by stable node ID.

func (*PlaneIndex) NodeByPath

func (index *PlaneIndex) NodeByPath(path ProjectionPath) (Node, bool, error)

NodeByPath looks up a node by its plane-specific path.

func (*PlaneIndex) Plane

func (index *PlaneIndex) Plane() string

Plane returns the plane name for this index.

type PlaneProvider

type PlaneProvider interface {
	Name() string
	Match(info DeviceInfo) bool
	CreatePlanes(info DeviceInfo) []Plane
}

type PortalIndex

type PortalIndex struct {
	// contains filtered or unexported fields
}

PortalIndex provides plane-scoped projection lookups for a single device entry.

Example:

portal, err := NewPortalIndex(entry.Projections())
if err != nil {
    return err
}
node, ok, err := portal.NodeByCanonical("Observability", ProjectionPath{
    Plane: ServicePlane,
    Segments: []PathSegment{
        {Name: "devices"},
        {Name: "boiler"},
    },
})
if err != nil {
    return err
}
if ok {
    _ = node.Path
}

func NewPortalIndex

func NewPortalIndex(projections []Projection) (PortalIndex, error)

NewPortalIndex builds a read-only portal index from projections.

Example:

portal, err := NewPortalIndex(entry.Projections())
if err != nil {
    return err
}

func PortalIndexForEntry

func PortalIndexForEntry(entry DeviceEntry) (PortalIndex, error)

PortalIndexForEntry builds a portal index from the entry projections.

func (PortalIndex) EdgeByID

func (portal PortalIndex) EdgeByID(plane string, edgeID EdgeID) (Edge, bool)

EdgeByID looks up an edge by plane and edge ID.

func (PortalIndex) NodeByCanonical

func (portal PortalIndex) NodeByCanonical(plane string, canonical ProjectionPath) (Node, bool, error)

NodeByCanonical looks up a node by plane and canonical Service path.

Example:

node, ok, err := portal.NodeByCanonical("Observability", ProjectionPath{
    Plane: ServicePlane,
    Segments: []PathSegment{
        {Name: "devices"},
        {Name: "boiler"},
    },
})

func (PortalIndex) PlaneIndex

func (portal PortalIndex) PlaneIndex(plane string) (*PlaneIndex, bool)

PlaneIndex returns the plane-specific index if present.

Example:

plane, ok := portal.PlaneIndex("Observability")
if ok {
    _ = plane.Plane()
}

type Projection

type Projection struct {
	Plane string
	Nodes []Node
	Edges []Edge
}

func NewProjection

func NewProjection(plane string, nodes []Node, edges []Edge) (Projection, error)

func (Projection) Validate

func (projection Projection) Validate() error

type ProjectionPath

type ProjectionPath struct {
	Plane    string
	Segments []PathSegment
}

func (ProjectionPath) String

func (path ProjectionPath) String() string

func (ProjectionPath) Validate

func (path ProjectionPath) Validate() error

type ProjectionProvider

type ProjectionProvider interface {
	CreateProjections(info DeviceInfo, planes []Plane) []Projection
}

type ScanBus

type ScanBus interface {
	Send(ctx context.Context, frame protocol.Frame) (*protocol.Frame, error)
}

type ServiceDeviceView

type ServiceDeviceView struct {
	Address         byte
	Addresses       []byte
	Manufacturer    string
	DeviceID        string
	SerialNumber    string
	MacAddress      string
	SoftwareVersion string
	HardwareVersion string
	Planes          []ServicePlaneView
}

ServiceDeviceView is a normalized device projection for shared service-layer use.

func ProjectDeviceEntry

func ProjectDeviceEntry(entry DeviceEntry) (ServiceDeviceView, error)

func ProjectRegistryDevices

func ProjectRegistryDevices(iter EntryIterator) ([]ServiceDeviceView, error)

type ServiceMethodView

type ServiceMethodView struct {
	Name             string
	ReadOnly         bool
	Primary          byte
	Secondary        byte
	Metadata         MethodMetadata
	ResponseSelector schema.SchemaSelector
}

ServiceMethodView is a normalized method projection for shared service-layer use.

func ProjectMethod

func ProjectMethod(method Method) (ServiceMethodView, error)

type ServicePlaneView

type ServicePlaneView struct {
	Name    string
	Methods []ServiceMethodView
}

ServicePlaneView is a normalized plane projection for shared service-layer use.

func ProjectPlane

func ProjectPlane(plane Plane) (ServicePlaneView, error)

type SlotRole

type SlotRole int
const (
	SlotRoleUnknown SlotRole = iota
	SlotRoleMaster
	SlotRoleSlave
)

type VerificationState

type VerificationState int
const (
	VerificationStateUnknown VerificationState = iota
	VerificationStateCandidate
	VerificationStateCorroborated
	VerificationStateIdentityConfirmed
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL