This solution was used to synchronize events between two applications: An eye tracking software, Python, and a stimulus control software, object Free Pascal/Delphi. It avoided a rewrite of the stimulus control app from ObjFPC/Delphi to Python.Pupil Eye Tracker sends some info via ZeroMQ to somewhere (I get used to call net stack to this place, please do not laugh). Indeed, a lot of info is sent, so they provided a way to filter this events; I have done this in a way that only timestamps should be sent.This is a dummy stand alone broadcast:    Broadcast dummy Pupil timestampsimport zmq#from ctypes import create_string_bufferfrom time import sleepdef test_msg():    test_msg = Pupil\ntimestamp:1389761135.56\n    return test_msgdef main():    context = zmq.Context()    socket = context.socket(zmq.PUB)    #address = create_string_buffer(tcp://127.0.0.1:5020,512)    address = tcp://127.0.0.1:5020    try:        #socket.bind(address.value)        socket.bind(address)    except zmq.ZMQError:        print Could not set Socket.    for i in range(120):        socket.send( test_msg() )        sleep(0.5)    context.destroy()if __name__ == '__main__':    main() A timestamp is the basic unit to synchronize frames, eye-gaze, events, basically all the stuff. Initially, they used the Python now() function to generate them. The current version can use the hardware of some cameras as well: Hardware time-stamping: Running Pupil Pro with Linux now uses hardware timestamps taken by the camera hardware at the start of exposure. Since they are using a Publisher-Subscriber protocol, Pupil Team suggested a way to receive this info. So, all I have done was to call the 'receive' client from inside the stimulus control app using a single thread for each call. Of course, it requires a broadcast server, in my case, during the real time Pupil Capture Server broadcast.unit client;{$mode objfpc}{$H+}interfaceuses   Classes , SysUtils , Process , FileUtil , zmqapi ;type  TShowStatusEvent = procedure(Status: String) of object;  { TClientThread }  TClientThread = class(TThread)  private    FServerAddress: string;    FContext : TZMQContext;      FSubscriber : TZMQSocket;    FMsg : string;    FTrialIndex : string;    FCode : string;    FTimestampsPath : string;    FOnShowStatus: TShowStatusEvent;    FTimestampsFile : TextFile;    function GetTimestampFromMessage(aMessage : Utf8String) : Utf8String;    procedure SetServerAddress(AValue: string);    procedure ShowStatus;  protected    procedure Execute; override;  public    constructor Create(TrialIndex : integer; Code, OutputPath : string; CreateSuspended : boolean = True);    destructor Destroy; override;    property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;    property ServerAddress : string read FServerAddress write SetServerAddress;  end;implementationconstructor TClientThread.Create(TrialIndex : integer; Code, OutputPath : string; CreateSuspended : boolean = True);begin  FreeOnTerminate := True;  FTrialIndex := IntToStr(TrialIndex);  FCode := Code;  FTimestampsPath := OutputPath;  FServerAddress := '127.0.0.1:5000';  ForceDirectoriesUTF8(ExtractFilePath(FTimestampsPath));    AssignFile(FTimestampsFile, FTimestampsPath);    if FileExistsUTF8(FTimestampsPath) then      Append(FTimestampsFile)    else Rewrite(FTimestampsFile);  inherited Create(CreateSuspended);end;destructor TClientThread.Destroy;begin  //  inherited Destroy;end;procedure TClientThread.ShowStatus;// this method is executed by the mainthread and can therefore access all GUI elements.begin  if Assigned(FOnShowStatus) then  begin    FOnShowStatus(FMsg);  end;end;function TClientThread.GetTimestampFromMessage(aMessage: Utf8String  ): Utf8String;  var aKey, aHeader : Utf8String;begin    aHeader := 'Pupil';    aKey := 'timestamp:';    Delete(  aMessage, Pos(aHeader, aMessage), Length(aHeader)  );    Delete(  aMessage, Pos(aKey, aMessage), Length(aKey)  );    while Pos(#10, aMessage) <> 0 do        Delete(  aMessage, Pos(#10, aMessage), Length(#10)  );    Result := aMessage;end;procedure TClientThread.SetServerAddress(AValue: string);begin  if FServerAddress = AValue then Exit;  if Length(AValue) > 0 then FServerAddress := AValue;end;procedure TClientThread.Execute;var  data : string;  message : UTF8String;begin  try    FContext := TZMQContext.Create;      FSubscriber := FContext.Socket( stSub );    FSubscriber.connect( 'tcp://' + FServerAddress);    FSubscriber.subscribe( '' );    FSubscriber.recv( message );    // ('value', 'value', 'value')    data := #40#39 + FTrialIndex + #39#44#32#39 + GetTimestampFromMessage(message) + #39#44#32#39 + FCode + #39#41;      Writeln(FTimestampsFile, data);    FMsg := data + #10#10 +            '' + FTimestampsPath + ''  + #10 +            '' + FServerAddress + '';    Synchronize( @Showstatus );  finally    CloseFile(FTimestampsFile);    FSubscriber.Free;      FContext.Free;    Terminate;  end;end;end.Call example:unit SomeForm;{$mode objfpc}{$H+}interfaceuses...    , client    ;...type    procedure CreateClientThread(Code : string);    procedure DebugStatus(msg : string);resourcestring    Event1 = 'Something happens';implementationprocedure CreateClientThread(Code : string);var  Client : TClientThread;  Filename : string;  SomeInteger: integer;begin  SomeInteger : -1;  Filename := GetCurrentDirUTF8 + '/timestamps';  Client := TClientThread.Create( SomeInteger, Code, FileName );  Client.OnShowStatus := @DebugStatus;  Client.ServerAddress := '127.0.0.1:5020';  Client.Start;end; procedure TSomeform.DebugStatus(msg: string);begin  ShowMessage(msg);end;  For post-facto analysis, is simple to read the yielded data, here timestamps_by_trial, from inside Pupil Player:def set_var(self, timestamps, current_path):    import os    import ast    self.timestamps = timestamps    self.frame_count = len(self.timestamps)    timestamps_by_trial_path = current_path + os.sep + 'timestamps'    timestamps_by_trial = [[]]    with open(timestamps_by_trial_path) as f:        for line in f:            temp = ast.literal_eval(line)            # temp = [0, 1, 2] = (trial_index, timestamp, a_code)            i = int(temp[0])            timestamp = (temp [1], temp[2])            if i > len(timestamps_by_trial):                timestamps_by_trial.append([])            timestamps_by_trial[i - 1].append(timestamp)        f.close    self.timestamps_by_trial = timestamps_by_trialBased on this data, we could draw some visual feedback too. The following picture shows the Trial Events in the bottom seek bar of Pupil Player:Note that the delphizmq binding was used in a scope with zmq_version 2.2.0 and under Unix OS's. The following is a debug Subscriber client.Please run this program from a terminal that you know will stay opened after execution.program zmq_subscriber;uses    SysUtils    , Classes    , zmqapi    ;const    UPDATE_COUNT = 1;var    context : TZMQContext;    subscriber : TZMQSocket;    i : integer;    address, filter : string;    message : Utf8String;    stringlist : TStringList;begin    // Socket to talk to server    address := '127.0.0.1:5020';    Writeln ( 'Connecting to Pupil server...' + #32#40 + 'address:' + #32 + address + #32#41);    context := TZMQContext.Create;    subscriber := Context.Socket( stSub );    subscriber.connect( 'tcp://' + address );    // You can choose a filter here    if ParamCount > 0 then        filter := ParamStr( 1 )    else        filter := '';    subscriber.subscribe( filter );    stringlist := TStringList.Create;    try        // \n = #10        stringlist.Delimiter := #10;        for i := 0 to UPDATE_COUNT - 1 do            begin                subscriber.recv( message );                stringlist.Clear;                stringlist.DelimitedText := message;                Writeln( stringlist.Text );            end;    finally        stringlist.Free;        subscriber.Free;        context.Free;           end;end. I have some questions:Since the Python code is just what you need to test the client, and a specific analytic example to give you the dimension of the application of this stuff, I am specially concerned about the Pascal code. I choose to call the client from inside a thread and to create a single thread every time an Event occur. Everything should run just fine, except when the granularity of the Events increase a lot in time, which becomes very processor consuming. Any comments about that would be appreciated.Is the code readable?
Sync eye movements with external events
python;multithreading;pascal;object pascal;zeromq