Low-level RPC interface

This section is an introduction into the low-level RPC interface of the Pulse Streamer. You can use this information for more deeper understanding of how to control Pulse Streamer hardware or to develop your own communication programs. We recommend to make use API described in the section Programming interface that greatly simplifies the pulse pattern generation.

Pulse Sequences

Note

The code examples given in this paragraph assume C++ programming language and use name conventions as in the firmware code. The naming may deviate from that used by high-level programming interface described in the section Programming interface. The data format given here applies only to the Pulse Streamer 8/2.

Pulse sequences are represented as one dimensional array of Pulse structures. Each Pulse specifies its duration and the states of the digital and analog output channels. The C++ data type is:

struct Pulse {
    uint_t32 ticks; // duration in ns
    uint_t8 digi; // bit mask
    int_t16 ao0;
    int_t16 ao1;
};

The duration ticks is specified in nanoseconds.

The lowest bit in the digital bit mask digi corresponds to channel 0, the highest bit to channel 7. A channel is high when its corresponding bit is 1 and low otherwise.

The analog values span the full signed 16 bit integer range, i.e. -1.0 V corresponds to -0x7fff and 1.0 V corresponds to 0x7fff. Note that the DAC resolution is 12 bits, i.e., the 4 LSB are ignored.

Resetting Pulse Streamer to constant outputs

You can reset the Pulse Streamer by using the method

void reset()

All outputs are set to 0V and all functional configurations are set to default. The automatic rearm functionality is enabled, the clock source is the internal clock of the device. No specific trigger functionality is enabled, which means that each sequence is streamed immediately when its upload is completed.

Setting constant outputs

You can set the outputs to a constant state.

The method is:

void constant(Pulse pulse=CONSTANT_ZERO)

CONSTANT_ZERO is a symbolic constant for a pulse with the value {0,0,0,0} Calling the method without a parameter will result in the default output state with all outputs set to 0V. If you set the device to a constant output an eventually currently streamed sequence is stopped. It is not possible to re-trigger the last streamed sequence after setting the Pulse Streamer constant.

Running pulse sequences

Running a pulse sequence corresponds to a single function call where you pass your pulse sequence as an argument.

You can repeat a pulse sequence infinitely or an integer number of times. A sequence run will start from the current constant output state.

The method to run a pulse sequence is:

void stream(std::vector<Pulse> sequence,
            int_t64 n_runs=INFINITE,
            Pulse final=Pulse{0,0,0,0},
            start_t start=IMMEDIATE
            )

sequence represents the pulse sequence

After the sequence has been repeated the given n_runs , the final output state will be reached.

The sequence is repeated infinitely if n_runs < 0 and a finite number of repetitions otherwise. INFINITE is a symbolic constant with the value -1. final represent the constant output after the sequence is finished (the tick values are ignored).

All parameters except ‘sequence’ have default values and can be omitted.

By default, the sequence is started immediately. Alternatively, you can tell the system to wait for a later software start command or for an external hardware trigger applied via

void setTrigger(start_t start, trigger_mode_t mode=NORMAL)

start is an enum with the mapping {IMMEDIATE:0, SOFTWARE:1, HARDWARE_RISING:2, HARDWARE_FALLING:3, HARDWARE_RISING_AND_FALLING:4} specifying how the stream should be started. If you have passed start=SOFTWARE, you can start the sequence using the method

void startNow()

If you want to trigger the Pulse Streamer by using the external trigger input of the device you have to pass HARDWARE_RISING (rising edge is the active trigger flank), HARDWARE_FALLING (falling edge is the active trigger flank) or HARDWARE_RISING_AND_FALLING (both edges are active) to the start argument. mode is an enum with the mapping{NORMAL:0, SINGLE:1}. If automatic rearm functionality is enabled (mode=NORMAL) you can re-trigger a successfully finished sequence, by the trigger mode you selected with the start argument. You can disable the automatic rearm by passing SINGLE to the mode argument.

If automatic rearm functionality is disabled, you can manually rearm the Pulse Streamer by using the method

void rearm()

After that you can re-trigger a successfully finished sequence exactly one time, by the trigger mode you selected with the ‘start’ argument.

You can check whether a sequence is available in memory by the following method

bool hasSequence()

The method returns true if a previous sequence is available.

You can check whether the Pulse Streamer is currently streaming by calling the method

bool isStreaming()

The method returns true if the Pulse Streamer is streaming a sequence. When the sequence is finished and the device remains in the final state, this method returns false again.

You can check whether the last sequence has finished successfully by calling the method.

bool hasFinished()

This method returns true if the Pulse Streamer remains in the final state after having finished a sequence.

The recommended way to stop the Pulse Streamer streaming is to set its output to a constant value via the method ‘constant()’, described above. However, if you want to stop a running sequence and force it to the dedicated final state, you can do this by calling the method

void forceFinal()

If no final state was declared in the current sequence, the output of Pulse Streamer will change to (or stay in) the last known constant state. Furthermore, if upload-performance is crucial to your application, you can call this function directly before streaming the next sequence. This will increase the upload-performance by about 20 percent.

More features

The Pulse Streamer can be fed in with three different clock sources. By default, the clock source is the internal clock of the device. It is also possible to feed in the system by an external clock of 125MHz (sampling clock) or an external 10MHz reference clock. You can choose the clock source via

void selectClock(clocking_t clock_source)

clock_source is an enum with {INTERNAL:0, EXT_125MHZ:1, EXT_10MHZ:2}.

If you want to get the serial number of your device or the ID-number of the built-in FPGA, you can call the method

std::string getSerial(serial_t serial=ID)

The method returns a hexadecimal string containing either the serial number/MAC-address or the ID-number of the FPGA depending of the value of the argument serial. Serial is an enum with the mapping {ID:0; MAC:1}.

If you want to get the version number of the current firmware you can call the method

std::string getFirmwareVersion()

Communicating with the instrument

Your Pulse Streamer 8/2 contains an embedded operating system. You connect to the embedded system over LAN through straight forward “Remote Procedure Calls” (RPC). Requests to the system are directly converted into C++ calls. This architecture gives you direct control over the system.

You can connect to the device via two RPC interfaces. (i) a JSON-RPC interface that is based on the well-established JSON data format (http://www.json.org/) (ii) a Google RPC interface (gRPC) that is based on Google’s data exchange format (https://developers.google.com/protocol-buffers/). Both RPC interfaces provide the same functionality, but the default and recommended communication protocol between the PC and the Pulse Streamer is JSON-RPC.

JSON-RPC Interface

JSON-RPC libraries are available for most software languages. More information can be found on the official website https://www.jsonrpc.org/ and on Wikipedia https://en.wikipedia.org/wiki/JSON-RPC.

The JSON-RPC URL of the Pulse Streamer is http://<pulse_streamer_ip>:8050/json-rpc, where <pulse_streamer_ip> is the IP address of your Pulse Streamer.

Sending Data over JSON-RPC

There is no native format for sending array data over JSON-RPC. Therefore, the pulse sequence is sent as a binary string. Since the HTTP transport layer requires string data to be base64 encoded, one conversion step is needed before sending a sequence. The JSON-RPC interface call can be performed with an array

{base64 string sequence,
 int_t64 n_runs,
 {uint_t32 ticks, uint_t8 digi, int_t16 ao0, int_t16 ao1},
}

or with named parameters

{"sequence":base64 string sequence,
 "n_runs":int_t64 n_runs,
 "final":{uint_t32 ticks, uint_t8 digi, int_t16 ao0, int_t16 ao1},
}

“sequence” is the array data as per above C++ data format definition packed into a binary string and converted to a base64 string. Please check out the Python example for connecting to the JSON-RPC server random_pulses_json.py. All other arguments are self-explanatory as per the above sections.

Other remote call methods are one to one correspondences to the C++ interface.

gRPC Interface

gRPC (https://grpc.io/) is a new RPC interface that is based on Google’s well established data exchange format called Protocol Buffers (https://developers.google.com/protocol-buffers/). There are gRPC libraries available for most programming languages. Note that gRPC requires the new Protobuf3 standard.

The gRPC server of the Pulse Streamer is <pulse_streamer_ip>:50051, where <pulse_streamer_ip> is the IP address of your Pulse Streamer.

Sending Data over gRPC

In gRPC, data types are defined by generic, language independent templates. The language specific implementation automatically takes care about conversion to native data types.

The Pulse Streamer interface looks like this.

package pulse_streamer;

message VoidMessage {}

message PulseMessage {
    uint32 ticks = 1;
    uint32 digi = 2;
    int32 ao0 = 3;
    int32 ao1 = 4;
}

message SequenceMessage {
    repeated PulseMessage pulse = 1;
    int64 n_runs = 2;
    PulseMessage final = 3;
}

message TriggerMessage {
    enum Start {
        IMMEDIATE = 0;
        SOFTWARE = 1;
        HARDWARE_RISING = 2;
        HARDWARE_FALLING = 3;
        HARDWARE_RISING_AND_FALLING = 4;
    }
    Start start = 1;
    enum Mode {
        NORMAL = 0;
        SINGLE = 1;
    }
    Mode mode = 2;
}

message ClockMessage {
    enum Clocking {
        INTERNAL = 0;
        EXT_125MHZ = 1;
        EXT_10MHZ = 2;
    }
    Clocking clock_source = 1;
}

message GetSerialMessage{
    enum Serial{
        ID=0;
        MAC=1;
    }
    Serial serial =1;
}

message PulseStreamerReply {
    uint32 value = 1;
}

message PulseStreamerStringReply {
    string string_value=1;
}

service PulseStreamer {
    rpc reset (VoidMessage) returns (PulseStreamerReply) {}
    rpc constant (PulseMessage) returns (PulseStreamerReply) {}
    rpc forceFinal (VoidMessage) returns (PulseStreamerReply) {}
    rpc stream (SequenceMessage) returns (PulseStreamerReply) {}
    rpc startNow (VoidMessage) returns (PulseStreamerReply) {}
    rpc setTrigger (TriggerMessage) returns (PulseStreamerReply) {}
    rpc rearm (VoidMessage) returns (PulseStreamerReply) {}
    rpc selectClock (ClockMessage) returns (PulseStreamerReply) {}
    rpc isStreaming (VoidMessage) returns (PulseStreamerReply) {}
    rpc hasSequence (VoidMessage) returns (PulseStreamerReply) {}
    rpc hasFinished (VoidMessage) returns (PulseStreamerReply) {}
    rpc getFirmwareVersion (VoidMessage) returns (PulseStreamerStringReply) {}
    rpc getSerial (GetSerialMessage) returns (PulseStreamerStringReply) {}
}