Overview

mKTL is a distributed messaging protocol designed for command and telemetry exchange between software components in observatory control systems. It is intended as the successor to the Keck Task Library (KTL) application programming interface (API) used at W. M. Keck Observatory (WMKO).

The basic logical unit of communication in KTL is a key/value pair. Each key/value pair may support request/response interactions (for retrieving or setting values) and publish/subscribe interactions (for receiving asynchronous updates). KTL systems are highly distributed: individual software components interface directly with hardware controllers or services and expose their functionality within a locally unique namespace. No central broker or authority is required for message routing.

mKTL messaging preserves these core design concepts while defining a modern messaging protocol that can be implemented consistently across multiple programming languages.

The protocol provides:

  • A key/value abstraction for commands and telemetry

  • Request/response messaging for synchronous interactions

  • Publish/subscribe messaging for asynchronous updates

  • A distributed architecture with no required brokers or intermediaries

  • A stable, versioned wire protocol suitable for multiple independent implementations

Core concepts

mKTL organizes commands and telemetry using a hierarchical namespace composed of stores and items. A store is a collection of items. It is effectively an associative array, providing little additional functionality beyond being a container. A store will have a unique name within its local namespace.

An item is the smallest addressable unit in mKTL, a single key/value pair that may correspond to a command, a configuration parameter, a telemetry value, an image buffer, and so on. Examples of items might include a power on/off control, a device operating mode, a temperature reading, a motor position, or the most recent image from a detector.

Each item has a key that uniquely identifies it within its store; the same item key can be reused in multiple stores. The store name and the item key combine to create the fully qualified key, which uniquely identifies an item within a local mKTL context.

A daemon is any application that is authoritative for one or more items, meaning, the daemon is responsible for publishing any new values, and for handling any requests to change the value of the item. A client is any application that interacts with an item for which it is not authoritative.

        flowchart LR

    linkStyle default stroke:#555555

    Client0[Client] -.- Item0
    Client0 -.- Item1
    Client1[Client] -.- Item1

    subgraph Store0 [Store]
    Item0(Item)
    Item1(Item)
    Item2(Item)
    end

    Item0 <--> Proto0([REQ+SUB<br>protocol])
    Item1 <--> Proto0
    Item2 <--> Proto0
    Proto0 <--> ZMQ0(((Transport)))

    style Client0 fill:#adf5ff,color:black
    style Client1 fill:#adf5ff,color:black
    style Item0 fill:#fff5ad,color:black,stroke:#888888
    style Item1 fill:#fff5ad,color:black,stroke:#888888
    style Item2 fill:#fff5ad,color:black,stroke:#888888
    style Proto0 fill:#ececff,color:black,stroke:#888888
    style Store0 fill:#ffffde,color:black,stroke:#888888
    style ZMQ0 fill:#ececff,color:black,stroke:#888888

    

From a high level view, an mKTL client solely interacts with mKTL items, contained within an mKTL store; no direct interactions with the wire protocol or the transport are required, though a client could forego all interface code and work strictly at the protocol+transport level.

        flowchart LR

    linkStyle default stroke:#555555

    ZMQ0(((Transport)))
    Proto0([REP+PUB<br>protocol])
    ZMQ0<-->Proto0

    subgraph Store0 [Store]
    Item0(Item)
    Item1(Item)
    Item2(Item)
    end

    Proto0 <--> Item0
    Proto0 <--> Item1
    Proto0 <--> Item2

    Item0 -.- Daemon0[Daemon]
    Item1 -.- Daemon0
    Daemon0 <--> Device0@{ shape: cyl, label: "Device<br>logic" }
    Item2 -.- Daemon1[Daemon]
    Daemon1 <--> Device1@{ shape: cyl, label: "Device<br>logic" }

    style Daemon0 fill:#dd8bd3,color:black,stroke:#888888
    style Daemon1 fill:#dd8bd3,color:black,stroke:#888888
    style Device0 fill:#dd8bd3,color:black,stroke:#888888
    style Device1 fill:#dd8bd3,color:black,stroke:#888888
    style Item0 fill:#fff5ad,color:black,stroke:#888888
    style Item1 fill:#fff5ad,color:black,stroke:#888888
    style Item2 fill:#fff5ad,color:black,stroke:#888888
    style Proto0 fill:#ececff,color:black,stroke:#888888
    style Store0 fill:#ffffde,color:black,stroke:#888888
    style ZMQ0 fill:#ececff,color:black,stroke:#888888


    

The perspective from an mKTL daemon is the mirror image, with daemons likewise not requiring any direct interactions with the protocol or transport, though again a daemon could bypass all interface code as long as it adheres to the mKTL protocol.