-- MichaelReese - 07 Feb 2019

In response to user requests for lower latency communication and fewer library dependencies in saftlib, API-breaking changes are introduced with major version 2 of saftlib. This Page is a migration guide for programs using saftlib. The version of saftlib described here is part of the enigma release.

A small presentation is available here.

Changes
  • saftlib 2.0 does not use DBus for interprocess communication, no dbus-daemon is needed for saftd to run.
  • saftd does not need root privileges.
  • Only a limited number (currently 32) of clients can be connected to saftd at the same time.
  • DBus debug tools can no longer be used, e.g. busctl (command line tool) and d-feet (GUI).
  • The command line tool saftbus-ctl can be used to watch saftd's managed objects.
  • saftlib 2.0 still depends on libsigc++ but glibmm dependece was dropped.
  • glibmm data types changed to C++ stl types in the saftlib API.
saftlib v1.4 saftlib v2.0 header
glib::RefPtr<T> std::shared_ptr<T> #include <memory>
Glib::init() // nothing -
Gio::init() // nothing -
Glib::ustring std::string #include <string>
g(u)int (unsigned) int #include <cstdint>
g(u)int8 (u)int8_t #include <cstdint>
g(u)int16 (u)int16_t #include <cstdint>
g(u)int32 (u)int32_t #include <cstdint>
g(u)int64 (u)int64_t #include <cstdint>
Glib::MainContext::get_default()->iteration(true) saftlib::wait_for_signal() #include <saftlib.h>
catch(Glib::Error &e) catch(saftbus::Error &e) #include <saftlib.h>
  • The special "A" array type in interfaces is no longer supported. The performance problem of the normal "a" array type was fixed by not using DBus anymore.
  • No notification signals are sent to Proxy objects when properties change. If such signals are needed, they have to be defined explicitly in the Interface xml description and implemented in the driver.
  • As a consequence of the previous point some additional signals were introduced to notify property changes:
    • TimingReceiver.xml
      • SigLocked (emitted when the Locked property changes)
    • FunctionGenerator.xml
      • SigEnabled (emitted when the Enabled property changes)
      • SigArmed (emitted when the Armed property changes)
      • SigRunning (emitted when the Running property changes)
  • UTC support (as requested by CCT https://www-acc.gsi.de/wiki/FAIR/CCT/Minutes160519 ): Additional interfaces are introduced for all signals/methods that use absolute time values. These additional interfaces use a dedicated type saftlib::Time instead of uint64_t. Please transform your code to use the new interfaces. The old interfaces using uint64_t as absolute time value are deprecated and will be removed after a transition period.
    • The new interface helps converting WhiteRabbit timestamps used by the timing system into UTC values that can be compared to the system time on front end computers.
    • saftlib v1.4:
      uint64_t TAItime = receiver->ReadCurrentTime();
      // You want UTC? ON YOUR OWN! 
      receiver->InjectEvent(id,param,TAItime);
    • saftlib v2.0:
      saftlib::Time time = receiver->CurrentTime();
      int64_t  UTCoffset = time.getUTCOffset(); // seconds between TAI and UTC 
      uint64_t TAItime   = time.getTAI();
      uint64_t UTCtime   = time.getUTC(); // Careful! This value is discontinuous at leap seconds!
                                        // But it is very close to your system time.
      bool leapSecondInterval = time.isLeapUTC(); // can tell you if you are in a leap second interval
      receiver->InjectEvent(id,param,time));                         // This event will be executed together ...
      receiver->InjectEvent(id,param,saftlib::makeTimeTAI(TAItime)); // ... with this event ...
      receiver->InjectEvent(id,param,saftlib::makeTimeUTC(UTCtime)); // ... and this event (outside leap seconds)
      receiver->InjectEvent(id,param,saftlib::makeTimeUTC(UTCtime,leapSecondInterval)); // This will be correct 
                                                                                        // even at leap seconds
      saftlib::Time ambiguous = 12345; // Forbidden! You have to say if you want UTC or TAI!
      saftlib::Time my_time_1 = saftlib::makeTimeUTC(12345); // ok
      saftlib::Time my_time_2 = saftlib::makeTimeTAI(12345); // ok
deprecated but still present signal/method (using these will generate Compiler warnings) use instead (with saftlib::Time)
uint64_t TimingReceiver::ReadCurrentTime saftlib::Time TimingReceiver::CurrentTime
TimingReceiver::InjectEvent(...,...,uint64_t) TimingReceiver::InjectEvent(...,...,saftlib::Time)
ActionSink::Late ActionSink::SigLate
ActionSink::Early ActionSink::SigEarly
ActionSink::Conflict ActionSink::SigConflict
ActionSink::Delayed ActionSinke::SigDelayed
FunctionGenerator::Started FunctionGenerator::SigStarted
FunctionGenerator::Stopped FunctionGenerator::SigStopped
MasterFunctionGenerator::Started MasterFunctionGenerator::SigStarted
MasterFunctionGenerator::Stopped MasterFunctionGenerator::SigStopped
MasterFunctionGenerator::AllStopped MasterFunctionGenerator::SigAllStopped
SoftwareConditoin::Action SoftwareCondition::SigAction
    • During the installation of saftlib, a text file with leap seconds is installed in saftlib-prefix/share/saftlib/leap-seconds.list. Where saftlib-prefix is the value given as you configure saftlib (e.g. ./configure --prefix=saftlib-prefix). All saftlib tools expect the leap second file at this location. If for some reason you need this file in a different location (e.g. /my-location/leap-second.list), you can specify the SAFTLIB_LEAPSECONDSLIST environment variable (export SAFTLIB_LEAPSECONDSLIST=/my-location/leap-second.list), and saftlib will look there for the file. If it doesn't find a file there, it will fallback to the default location. If it doesn't find the file there, it will throw a std::runtime_error. The file is read only once for each process using saftlib whenever one of the following functions is called for the first time: saftlib::init(), saftlib::Time::getUTC(), XXX_Proxy::create(...). You normally don't have to call saftlib::init() explicitly because most programs will create a Proxy on startup. If the file is outdated, you either have to update saftlib and reinstall it or manually copy a recent version of the file. This file can be found at one of the locations listed here https://kb.meinbergglobal.com/kb/time_sync/ntp/configuration/ntp_leap_second_file#sites_providing_a_leap_second_file.

  • The FunctionGenerator interface in saftlib v2.0 has been extended:
    • There is now a saftlib object called FunctionGeneratorFirmware. A Proxy of this object can be used to trigger a rescan of attached FunctionGenerator channels (for example after a MIL channel was added to the system). This is only possible if none of the channels is active.
      std::shared_ptr<TimingReceiver_Proxy> receiver;
      //... get receiver proxy ...
      map<std::string, std::string> fg_fw = receiver->getInterfaces()["FunctionGeneratorFirmware"];
      if (fg_fw.empty()) { /* error */ }
      std::shared_ptr<FunctionGeneratorFirmware_Proxy> fg_firmware 
                   = FunctionGeneratorFirmware_Proxy::create(fg_fw.begin()->second);
      try {
        auto scanning_result = fg_firmware->Scan(); // will throw if any channel is active
        // there is also ScanMasterFg() and ScanFgChannels(). Scan() is an alias for ScanMasterFg().
        for (auto &pair: scanning_result) {
          // pair.first  = channel name
          // pari.second = object path, needed to create a Proxy.
          std::cout << "  " << pair.first << " " << pair.second << std::endl;
        }
      } catch(...) { /* ... */ }
    • It is not possible to have the individual FunctionGenerator channels at the same time as the MasterFunctionGenerator. By default, the MasterFunctionGenerator is provided. If individual channels are needed, one has to call saft-fg-ctl -d tr0 -si to initiate a rescan of the available channels and providing objects for individual channels. saft-fg-ctl -d tr0 -sm does a rescan and provides the MasterFunctionGenerator object.

Event loops and signals

In saftlib 1.4 there were multiple ways to write event loops that dispatch incoming signals. Here are some examples:
  • Glib::RefPtr<Glib::MainLoop> loop = Glib::MainLoop::create();
    loop->run();
  • Glib::RefPtr<Glib::MainLoop> loop = Glib::MainLoop::create();
    while(true) {
      loop->get_context()->iteration(true);
    }
  • while(true) {
      Glib::MainContext::get_default()->iteration(true);
    }

In saftlib 2 there is a different way to write an event loop: use the function

int wait_for_signal(int timeout_ms=-1)

which will block until the next saftlib signal arrives, or the timeout is hit (timeout of -1 waits forever).
while(true) {
  saftlib::wait_for_signal();
}

It is possible to assign saftlib::Proxy objects to so called saftlib::SignalGroups. This is useful for multi threadded applications, where each thread manages a different set of saftlib::Proxy objects.

saftlib::SignalGroup fgSignalGroup;
std::shared_ptr<FunctionGenerator_Proxy> my_fg_0 = FunctionGenerator_Proxy::create("/de/gsi/saftlib/tr0/fg_0", fgSignalGroup);
std::shared_ptr<FunctionGenerator_Proxy> my_fg_1 = FunctionGenerator_Proxy::create("/de/gsi/saftlib/tr0/fg_1", fgSignalGroup);
while(true) {
  fgSignalGroup.wait_for_signal(); // wait for signals from my_fg_0 and my_fg_1
}

If no SignalGroup object is passed to the XXX_Proxy::create() function, then the Proxy is assigned to the saftlib::globalSignalGroup, and saftblib::wait_for_signal() is equivalent to saftlib::globalSignalGroup::wait_for_signal(). A Proxy can only be assigned to one SignalGroup.

If Signals from a Proxy are not used, i.e. wait_for_signal() is never called, the Proxy should be created with the saftlib::noSignals SignalGroup. That prevents saftd from sending any signals to this Proxy object.
Differences between Glib::RefPtr and std::shared_ptr

Use of std::shared_ptr<T> ist largely identical to Glib::RefPtr<T>, but there are exceptions:
  • Casting Glib::RefPtr
    • Glib::RefPtr<TargetType> casted_ref_ptr = Glib::RefPtr<TargetType>::cast_dynamic(ref_ptr);
    • Glib::RefPtr<TargetType> casted_ref_ptr = Glib::RefPtr<TargetType>::cast_const(ref_ptr);
    • Glib::RefPtr<TargetType> casted_ref_ptr = Glib::RefPtr<TargetType>::cast_static(ref_ptr);

  • Casting std::shared_ptr
    • std::shared_ptr<TargetType> casted_ptr = std::dynamic_pointer_cast<TargetType>(ptr);
    • std::shared_ptr<TargetType> casted_ptr = std::const_pointer_cast<TargetType>(ptr);
    • std::shared_ptr<TargetType> casted_ptr = std::static_pointer_cast<TargetType>(ptr);

  • Decrement reference count and set underlying instance to zero
    • Glib::RefPtr::reset() or Glib::RefPtr::clear() which is deprecated
    • std::shared_ptr::reset()

Example of converting a program from saftlib 1.4 to saftlib 2.0

Side by side comparison of a program using saftlib 1.4 and saftlib 2.0. It measures the time it takes from event injection to callback function execution for the injected event.
saftlib 1.4 saftlib 2.0
// This program measures the time it takes from 
// injecting a timing event into the ECA using 
// saftlib API until the execution of the callback
// function that is connected to the saftlib signal
// caused by the injected event.

// C++ std header
#include <iostream>
#include <fstream>
#include <algorithm>
#include <iterator>
#include <cstdint>
#include <memory>

// saftlib header
#include <saftlib/SAFTd.h>
#include <saftlib/TimingReceiver.h>
#include <saftlib/SoftwareActionSink.h>
#include <saftlib/SoftwareCondition.h>

// linux header
#include <time.h>

// some global variables 
const unsigned number_of_samples = 50000;
struct timespec start_time, stop_time;
int measurement_counter = 0;
Glib::RefPtr<saftlib::TimingReceiver_Proxy> timing_receiver_proxy;
std::vector<int> histogram(3000); // histogram with 1 microsecond bin width
bool keep_measuring = true;
std::vector<int> samples(number_of_samples);

// callback function for the injected event
void on_action(const uint64_t& event,
              const uint64_t& param,
              const uint64_t& deadline,
              const uint64_t& executed,
              const uint16_t& flags)
{
   // get stop time
   clock_gettime( CLOCK_REALTIME, &stop_time);
   // calculate round trip time in units of microseconds
   double round_trip_time = ( stop_time.tv_sec - start_time.tv_sec )*1000000.
                       + ( stop_time.tv_nsec - start_time.tv_nsec )/1000.;

   // assume that the signal flight time is half the round trip time
   int bin = round_trip_time/2;

   // make a histogram of all measurements
   if (bin >= 0 && bin < static_cast<int>(histogram.size()))
   {
      ++histogram[bin];
      samples[measurement_counter] = bin;
   }

   // terminate the measurement as the required 
   // number of samples is reached
   ++measurement_counter;
   if (measurement_counter == number_of_samples) {
      keep_measuring = false;
   }
}

int main()
{
   try
   {
      Glib::init();
      Gio::init();
      Glib::RefPtr<saftlib::SAFTd_Proxy> saftd_proxy
         = saftlib::SAFTd_Proxy::create();

      std::map<Glib::ustring, Glib::ustring> devices
         = saftd_proxy->getDevices();

      // assume that device name "tr0" exists
      std::string timing_receiver_object_path 
         = devices["tr0"]; 

      timing_receiver_proxy
         = saftlib::TimingReceiver_Proxy::create(timing_receiver_object_path);

      // create software action sink, the empty string as 
      // argument will result in a random name for the sink
      std::string software_action_sink_object_path
         = timing_receiver_proxy->NewSoftwareActionSink("");

      Glib::RefPtr<saftlib::SoftwareActionSink_Proxy> software_action_sink_proxy
         = saftlib::SoftwareActionSink_Proxy::create(software_action_sink_object_path);

      // configuration parameters for the new software condition
      bool     active   = false;
      uint64_t id       = UINT64_C(0xffff000000000000);
      uint64_t mask     = UINT64_C(0xffffffffffffffff);
      int64_t  offset   = INT64_C(0x0);
      uint64_t param    = UINT64_C(0x0);
      uint64_t deadline = UINT64_C(0x0);

      std::string software_condition_object_path
         = software_action_sink_proxy->NewCondition(active, id, mask, offset);

      Glib::RefPtr<saftlib::SoftwareCondition_Proxy> software_condition_proxy 
         = saftlib::SoftwareCondition_Proxy::create(software_condition_object_path);

      // configure the software_condition
      software_condition_proxy->Action.connect(sigc::ptr_fun(&on_action));
      software_condition_proxy->setAcceptLate(true);
      software_condition_proxy->setAcceptEarly(true);
      software_condition_proxy->setAcceptConflict(true);
      software_condition_proxy->setAcceptDelayed(true);
      software_condition_proxy->setActive(true);

      Glib::RefPtr<Glib::MainLoop> loop = Glib::MainLoop::create();

      // program main loop, terminated by the callback function 
      while (keep_measuring) {
         // take the start time of the round trip measurmenet
         clock_gettime( CLOCK_REALTIME, &start_time);
         // inject the event that will trigger our "on_action" callback function
         timing_receiver_proxy->InjectEvent(id, param, deadline);

         // blocking wait for next saftlib signal
         // the callback takes the stop time, and 
         // calculates the round trip time
         loop->get_context()->iteration(true);
      }

      // write histogram to file;
      std::ofstream hist("histogram.dat");
      std::copy(histogram.begin(), 
               histogram.end(), 
               std::ostream_iterator<int>(hist,"\n"));

      // write measured samples to file;
      std::ofstream samp("samples.dat");
      std::copy(samples.begin(), 
               samples.end(), 
               std::ostream_iterator<int>(samp,"\n"));
   }
   catch (const Glib::Error& error) 
   {
      std::cerr << "Failed to invoke method: " << error.what() << std::endl;
   }

   return 0;
}
// This program measures the time it takes from 
// injecting a timing event into the ECA using 
// saftlib API until the execution of the callback
// function that is connected to the saftlib signal
// caused by the injected event.

// C++ std header
#include <iostream>
#include <fstream>
#include <algorithm>
#include <iterator>
#include <cstdint>
#include <memory>

// saftlib header
#include <saftlib/SAFTd.h>
#include <saftlib/TimingReceiver.h>
#include <saftlib/SoftwareActionSink.h>
#include <saftlib/SoftwareCondition.h>

// linux header
#include <time.h>

// some global variables 
const unsigned number_of_samples = 50000;
struct timespec start_time, stop_time;
int measurement_counter = 0;
std::shared_ptr<saftlib::TimingReceiver_Proxy> timing_receiver_proxy;
std::vector<int> histogram(3000); // histogram with 1 microsecond bin width
bool keep_measuring = true;
std::vector<int> samples(number_of_samples);

// callback function for the injected event
void on_action(const uint64_t& event,
              const uint64_t& param,
              const saftlib::Time& deadline,
              const saftlib::Time& executed,
              const uint16_t& flags)
{
   // get stop time
   clock_gettime( CLOCK_REALTIME, &stop_time);
   // calculate round trip time in units of microseconds
   double round_trip_time = ( stop_time.tv_sec - start_time.tv_sec )*1000000.
                       + ( stop_time.tv_nsec - start_time.tv_nsec )/1000.;

   // assume that the signal flight time is half the round trip time
   int bin = round_trip_time/2;

   // make a histogram of all measurements
   if (bin >= 0 && bin < static_cast<int>(histogram.size()))
   {
      ++histogram[bin];
      samples[measurement_counter] = bin;
   }

   // terminate the measurement as the required 
   // number of samples is reached
   ++measurement_counter;
   if (measurement_counter == number_of_samples) {
      keep_measuring = false;
   }
}

int main()
{
   try
   {


      std::shared_ptr<saftlib::SAFTd_Proxy> saftd_proxy
         = saftlib::SAFTd_Proxy::create();

      std::map<std::string, std::string> devices
         = saftd_proxy->getDevices();

      // assume that device name "tr0" exists
      std::string timing_receiver_object_path 
         = devices["tr0"]; 

      timing_receiver_proxy
         = saftlib::TimingReceiver_Proxy::create(timing_receiver_object_path);

      // create software action sink, the empty string as 
      // argument will result in a random name for the sink
      std::string software_action_sink_object_path
         = timing_receiver_proxy->NewSoftwareActionSink("");

      std::shared_ptr<saftlib::SoftwareActionSink_Proxy> software_action_sink_proxy
         = saftlib::SoftwareActionSink_Proxy::create(software_action_sink_object_path);

      // configuration parameters for the new software condition
      bool     active   = false;
      uint64_t id       = UINT64_C(0xffff000000000000);
      uint64_t mask     = UINT64_C(0xffffffffffffffff);
      int64_t  offset   = INT64_C(0x0);
      uint64_t param    = UINT64_C(0x0);
      saftlib::Time deadline;

      std::string software_condition_object_path
         = software_action_sink_proxy->NewCondition(active, id, mask, offset);

      std::shared_ptr<saftlib::SoftwareCondition_Proxy> software_condition_proxy 
         = saftlib::SoftwareCondition_Proxy::create(software_condition_object_path);

      // configure the software_condition
      software_condition_proxy->SigAction.connect(sigc::ptr_fun(&on_action));
      software_condition_proxy->setAcceptLate(true);
      software_condition_proxy->setAcceptEarly(true);
      software_condition_proxy->setAcceptConflict(true);
      software_condition_proxy->setAcceptDelayed(true);
      software_condition_proxy->setActive(true);



      // program main loop, terminated by the callback function 
      while (keep_measuring) {
         // take the start time of the round trip measurmenet
         clock_gettime( CLOCK_REALTIME, &start_time);
         // inject the event that will trigger our "on_action" callback function
         timing_receiver_proxy->InjectEvent(id, param, deadline);

         // blocking wait for next saftlib signal
         // the callback takes the stop time, and 
         // calculates the round trip time
         saftlib::wait_for_signal();
      }

      // write histogram to file;
      std::ofstream hist("histogram.dat");
      std::copy(histogram.begin(), 
               histogram.end(), 
               std::ostream_iterator<int>(hist,"\n"));

      // write measured samples to file;
      std::ofstream samp("samples.dat");
      std::copy(samples.begin(), 
               samples.end(), 
               std::ostream_iterator<int>(samp,"\n"));
   }
   catch (const saftbus::Error& error) 
   {
      std::cerr << "Failed to invoke method: " << error.what() << std::endl;
   }

   return 0;
}

Performance difference

The programs shown above produce the following histograms (measured on SCU3). The measurement of 1/2 of the round trip time is an approximation of the saftlib signal latency (time difference between the occurrence of an event and the notification of the user process).

The measurements below were done without using realtime scheduling, i.e. no SCHED_RR or SCHED_FIFO was used on either saftd, dbus-daemon, or the measurement application. Under these conditions, saftlib 2.0 is about 5 times faster than previous DBus based versions.
  • histograms of 1/2 of the round trip time: 50000 measurements of time between injecting an event into the ECA until execution of the callback function for the injected event:
    saftlib1v2.png

Commandline tool examples

There is a command line tool saftbus-ctl, similar to busctl for DBus. It is installed together with saftlib tools and can be used to display the status of the saftbus. It allows to get/set properties and call methods of saftbus objects. Introspection of saftbus objects as in DBus is not possible (yet), i.e. the interface (property/method name and type signature) has to be known. The interface description can be found in the .xml files of the saftlib/interface/ subdirectory. The type signature is specified using the same ascii characters as in the .xml interface definition (plus 'v' for void): byte='y', bool='b', int16='n', uint16='q', int32='i', uint32='u', int64='x', uint64='t', double='d', string='s', void='v', array_of_<X>='a<X>', associative_array<K><V>='a{KV}'

List all attached devices
$ saftbus-ctl --get-property de.gsi.saftlib.SAFTd /de/gsi/saftlib Devices a{ss}
tr0:/de/gsi/saftlib/tr0 

Show all interfaces of a TimingReceiver
$ saftbus-ctl --get-property de.gsi.saftlib.TimingReceiver /de/gsi/saftlib/tr0 Interfaces a{sa{ss}}
EmbeddedCPUActionSink -> embedded_cpu:/de/gsi/saftlib/tr0/embedded_cpu 
FunctionGenerator -> fg-2-0:/de/gsi/saftlib/tr0/fg_firmware/fg_0 fg-2-1:/de/gsi/saftlib/tr0/fg_firmware/fg_1 fg-3-0:/de/gsi/saftlib/tr0/fg_firmware/fg_2 fg-3-1:/de/gsi/saftlib/tr0/fg_firmware/fg_3 fg-4-0:/de/gsi/saftlib/tr0/fg_firmware/fg_4 fg-4-1:/de/gsi/saftlib/tr0/fg_firmware/fg_5 fg-5-0:/de/gsi/saftlib/tr0/fg_firmware/fg_6 fg-5-1:/de/gsi/saftlib/tr0/fg_firmware/fg_7 
FunctionGeneratorFirmware -> fg_firmware:/de/gsi/saftlib/tr0/fg_firmware 
Input -> B1:/de/gsi/saftlib/tr0/inputs/B1 B2:/de/gsi/saftlib/tr0/inputs/B2 
MasterFunctionGenerator -> masterfg:/de/gsi/saftlib/tr0/fg_firmware/masterfg 
Output -> B1:/de/gsi/saftlib/tr0/outputs/B1 B2:/de/gsi/saftlib/tr0/outputs/B2 
SCUbusActionSink -> scubus:/de/gsi/saftlib/tr0/scubus 
SoftwareActionSink -> _3:/de/gsi/saftlib/tr0/software/_3 

Show all SoftwareActionSinks of a TimingReceiver
$ saftbus-ctl --get-property de.gsi.saftlib.TimingReceiver /de/gsi/saftlib/tr0 SoftwareActionSinks 'a{ss}'
_3:/de/gsi/saftlib/tr0/software/_3 

Read the WR Time from a TimingReceiver
 $ saftbus-ctl --call de.gsi.saftlib.TimingReceiver /de/gsi/saftlib/tr0 ReadCurrentTime t v 
1556190631969662695

Inject an event into ECA (same as saft-ctl tr0 inject 0 0 0)
$ saftbus-ctl --call de.gsi.saftlib.TimingReceiver /de/gsi/saftlib/tr0 InjectEvent v ttt 0 0 `saftbus-ctl --call de.gsi.saftlib.TimingReceiver /de/gsi/saftlib/tr0 ReadCurrentTime t v`

Scan available function generator channels
$ saftbus-ctl --call  de.gsi.saftlib.FunctionGeneratorFirmware  /de/gsi/saftlib/tr0/fg_firmware Scan a{ss} v                  
fg-2-0:/de/gsi/saftlib/tr0/fg_firmware/fg_0 fg-2-1:/de/gsi/saftlib/tr0/fg_firmware/fg_1 fg-3-0:/de/gsi/saftlib/tr0/fg_firmware/fg_2 fg-3-1:/de/gsi/saftlib/tr0/fg_firmware/fg_3 fg-4-0:/de/gsi/saftlib/tr0/fg_firmware/fg_4 fg-4-1:/de/gsi/saftlib/tr0/fg_firmware/fg_5 fg-5-0:/de/gsi/saftlib/tr0/fg_firmware/fg_6 fg-5-1:/de/gsi/saftlib/tr0/fg_firmware/fg_7
Topic attachments
I Attachment Action Size Date Who Comment
saftlib1v2.pngpng saftlib1v2.png manage 5 K 08 Feb 2019 - 12:27 MichaelReese histograms of 1/2 of the round trip time: Injecting an event into the ECA until execution of the callback function for the injected event
saftlib_2-0_migration.pdfpdf saftlib_2-0_migration.pdf manage 149 K 05 Jun 2019 - 13:39 DietrichBeck saftlib migration including UTC
Topic revision: r30 - 12 Nov 2019, MichaelReese
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Foswiki? Send feedback