Low-level RPC interface
This section introduces the low-level RPC interface of the Pulse Streamer. You can use this information to understand how to control the Pulse Streamer hardware or to develop your own communication programs. We recommend making use of the API described in the section Programming interface that significantly simplifies the pulse pattern generation.
Pulse Sequences
Note
The code examples provided in this section are in C++ and naming conventions can differ from the Programming interface section. The data format given here applies only to the Pulse Streamer 8/2.
Pulse sequences are represented as a 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
are 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 0 V, 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 0 V. If you set the device to a constant output, an eventually
currently streamed sequence is stopped. It is not possible to retrigger 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}
)
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 value of tick
is 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 retrigger 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 retrigger 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 finishing 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 set in the current sequence, the output of Pulse Streamer will change to (or stay in) the last known constant state.
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, you can call the method
std::string getSerial()
The method returns a string containing the serial number/MAC-address of your Pulse Streamer 8/2.
If you want to get the ID-number of the built-in FPGA, you can call the method
std::string getFPGAID()
The method returns a hexadecimal string containing the ID-number of the built-in FPGA.
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 straightforward “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. You can find more information 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 of the 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 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 (VoidMessage) returns (PulseStreamerStringReply) {}
rpc getFPGAID (VoidMessage) returns (PulseStreamerStringReply) {}
}