Quick and dirty client application for windows

Forum for development in the C++ programming language.

Quick and dirty client application for windows

Postby MastaLomaster » 09 Mar 2014, 01:45

DON'T read this post if:
- you are happy with installing multi-Magabyte set of "Boost" libraries and injecting something from there into your application
- you are happy with handling exceptions thrown from some "Boost" element
- you like new language extentions, like BOOST_FOREACH
- you are happy with CMake and its fantastic syntax
- you like to link MSVCRTxx.DLL with the Boost libraries and 100% sure they use the same heap and threads handling
- the application you've made using SDK is extremely stable

Otherwise please take a look at the small console application (190 lines of code) made in MS Visual Studio 10 SP1.
It connects to the local eyetracker and prints the screen coordinates until you press the 'Enter' button.

It does not need any of the SDK headers/libraries. And this is plain C, not really C++.

Of course, it has some disadvantages. Not much error checking and a lot of assumptions.

You may tell me:

"What if the recv() function return only a part of the frame? Or two frames together?"
It can. Theoretically. But in practice, if you work with loopback interface, this will never happen. Besides, the frame is never larger than minimal TCP MTU.
So you only spent extra CPU cycles and programming work trying to serialize what is already serialized.

"You don't parse every single bracket"
Sure. I don't need it. The scanf() function is enough to parse these JSON data.

"What if they slightly change the frame format / protocol in the future?"
Ok, then this app will not work. But you'll now how it works and will easily fix it.

Here is the code. It works.

Your comments are welcome.

Code: Select all
#include <Windows.h>
#include <stdio.h>
#include <process.h>
#include <winsock.h>

// The only two messages that we send to the server
char *JSON_heart_beat="{\"category\":\"heartbeat\"}";
char *JSON_set_push="{\"category\":\"tracker\",\"request\":\"set\",\"values\":{\"push\":true,\"version\":1}}";
// Scanf template for the frame (containing "fix:false")
char *JSON_frame_false="{\"category\":\"tracker\",\"request\":\"get\",\"statuscode\":200,\"values\":{\"frame\":{"\
             "\"avg\":{\"x\":%f,\"y\":%f},\"fix\":false,\"lefteye\":{\"avg\":{\"x\":%f,\"y\""\
             ":%f},\"pcenter\":{\"x\":%f,\"y\":%f},\"psize\":%f,\"raw\":{\"x\":%f"\
             ",\"y\":%f}},\"raw\":{\"x\":%f,\"y\":%f},\"righteye\":{\"avg\":{\"x\":%f"\
             ",\"y\":%f},\"pcenter\":{\"x\":%f,\"y\":%f},\"psize\":%f,\"raw\":{\"x\":%f"\
             ",\"y\":%f}},\"state\":%d,\"time\":%d}}}";
// Scanf template for the frame (containing "fix:true")
char *JSON_frame_true="{\"category\":\"tracker\",\"request\":\"get\",\"statuscode\":200,\"values\":{\"frame\":{"\
             "\"avg\":{\"x\":%f,\"y\":%f},\"fix\":true,\"lefteye\":{\"avg\":{\"x\":%f,\"y\""\
             ":%f},\"pcenter\":{\"x\":%f,\"y\":%f},\"psize\":%f,\"raw\":{\"x\":%f"\
             ",\"y\":%f}},\"raw\":{\"x\":%f,\"y\":%f},\"righteye\":{\"avg\":{\"x\":%f"\
             ",\"y\":%f},\"pcenter\":{\"x\":%f,\"y\":%f},\"psize\":%f,\"raw\":{\"x\":%f"\
             ",\"y\":%f}},\"state\":%d,\"time\":%d}}}";

SOCKET TETSocket;

volatile bool flag_ShutDownThreads=false; // flag to inform the threads to terminate

uintptr_t handler_HeartBeat=0, handler_Reader=0; // thread handlers

//==================================================
// HeartBeat thread - sends a message once a second
//==================================================
unsigned __stdcall HeartBeatThread(void *p)
{
   while(!flag_ShutDownThreads)
   {
      if(SOCKET_ERROR==send(TETSocket,JSON_heart_beat,strlen(JSON_heart_beat),0)) return 0; // Connection closed
      Sleep(1000);
   }
   return 0;
}

//=========================================================
// Reader thread - receiving messages from the server here
//=========================================================
unsigned __stdcall ReaderThread(void *p)
{
   // big enough buffers...
   char buffer[4096];
   char true_false_buffer[128];

   int bytesReceived;
   int num_scanned=0;

   float x_avg,y_avg,
      left_x_avg, left_y_avg, left_pcenter_x, left_pcenter_y, left_psize, left_x_raw, left_y_raw,
      x_raw, y_raw,
      right_x_avg, right_y_avg, right_pcenter_x, right_pcenter_y, right_psize, right_x_raw, right_y_raw;
   int state, tet_time, fix;

   while(!flag_ShutDownThreads)
   {
      bytesReceived = recv(TETSocket, buffer, 4095, 0);
      if(0>=bytesReceived) return 0; // Connection closed
      buffer[bytesReceived]=0; // Make a string zero-terminated
      
      
      // First try. Template string contains "fix:false"
      fix=0;
      num_scanned=sscanf(buffer,JSON_frame_false,
         &x_avg,&y_avg,
         &left_x_avg, &left_y_avg, &left_pcenter_x, &left_pcenter_y, &left_psize, &left_x_raw, &left_y_raw,
         &x_raw, &y_raw,
         &right_x_avg, &right_y_avg, &right_pcenter_x, &right_pcenter_y, &right_psize, &right_x_raw, &right_y_raw,
         &state, &tet_time
         );
      // Second try. Template string contains "fix:true"
      if(20!=num_scanned)
      {
         fix=1;
         num_scanned=sscanf(buffer,JSON_frame_true,
         &x_avg,&y_avg,
         &left_x_avg, &left_y_avg, &left_pcenter_x, &left_pcenter_y, &left_psize, &left_x_raw, &left_y_raw,
         &x_raw, &y_raw,
         &right_x_avg, &right_y_avg, &right_pcenter_x, &right_pcenter_y, &right_psize, &right_x_raw, &right_y_raw,
         &state, &tet_time
         );
      }

      if(20==num_scanned)
      {
         // Do domething real here instead of just printing
         //if(7==state)
         printf("X:%f Y:%f fix:%d state:%d\n",x_avg,y_avg,fix,state);
      }
   }
   return 0;
}

//=========================================================
// Init TCP connection (socket)
//=========================================================
int TETconnect()
{
   WORD sockVersion;
   WSADATA wsaData;
   int nret;
   char *err_string;
   
   SOCKADDR_IN TETserver;

   // Initialize Winsock
   sockVersion=MAKEWORD(2, 2);         

   nret=WSAStartup(sockVersion, &wsaData);
   if(nret)
   {
      err_string="Failed to initialize Winsock\n";
      goto ws_error;
   }

   // Create a socket
   TETSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);      
   if (INVALID_SOCKET == TETSocket)
   {
      err_string="Cannot create socket\n";
      goto ws_error;
   }

   // Local server
   TETserver.sin_family = AF_INET;
   TETserver.sin_addr.S_un.S_un_b.s_b1=127; // 127.0.0.1
   TETserver.sin_addr.S_un.S_un_b.s_b2=0;
   TETserver.sin_addr.S_un.S_un_b.s_b3=0;
   TETserver.sin_addr.S_un.S_un_b.s_b4=1;
   TETserver.sin_port = htons(6555);

   // Connect the local server
   nret = connect(TETSocket, (LPSOCKADDR)&TETserver, sizeof(struct sockaddr));
   if (SOCKET_ERROR == nret)
   {
      err_string="Cannot connect to server\n";
      goto ws_error;
   }

   // Connected!!!
   fprintf(stderr,"Connected to server!\n");
   return 0;

ws_error:
   // Print error and shut down winsock
   fprintf(stderr,"%s. Press 'Enter' to close the program.\n", err_string);
   WSACleanup();
   return 1;
}

int main()
{
   char buffer[4096];

   // 1. Initialize TCP connection
   if(TETconnect()) goto cleanup;

   // 2. Set Push mode
   send(TETSocket,JSON_set_push,strlen(JSON_set_push),0);
   recv(TETSocket, buffer, 4095, 0); // ignore the reply

   // 2. Start heartbeat thread
   handler_HeartBeat=_beginthreadex(NULL,0,HeartBeatThread,0,0,NULL);
   if(1>handler_HeartBeat) goto cleanup; // this will never happen... but...
      
   // 3. Start Reader thread
   handler_Reader=_beginthreadex(NULL,0,ReaderThread,0,0,NULL);
   if(1>handler_Reader) goto cleanup; // this will never happen... but...
   
cleanup:
   //press 'Enter' to end the program
   getc(stdin);
   
   // Shut down winsock
   WSACleanup();

   // Wait for threads to terminate
   flag_ShutDownThreads=true; // inform the threads to terminate
   if(handler_HeartBeat) WaitForSingleObject((HANDLE)handler_HeartBeat,0);
   if(handler_Reader) WaitForSingleObject((HANDLE)handler_Reader,0);

   return 0;
}
MastaLomaster
 
Posts: 37
Joined: 03 Mar 2014, 16:35

Re: Quick and dirty client application for windows

Postby Martin » 10 Mar 2014, 23:23

Thanks for posting. I'll forward your comments to the dev team to see if we can make it easier or a more lightweight SDK. Meanwhile, there has been some additional work on the current C++ SDK which will go on Github in the next couple of days.

As for the Boost dependency, there are several alternatives, one could be C++ Sockets Library. It's rather important that it builds on several platforms, that's why we went with the Boost libraries.
Martin
 
Posts: 567
Joined: 29 Oct 2013, 15:20

Re: Quick and dirty client application for windows

Postby wxz » 07 Aug 2014, 18:49

I'm curious about the heartbeat thread. It sends the message once every second. But in the API reference, it says "heartbeats must be sent at a rate matching the heartbeatinterval value of the tracker category." In other words, if the data is 30 fps, the heartbeat should be sent every 1000/30 ms.

I didn't try out the code, but I'm curious if it works, does that mean we can send the heartbeat at a lower rate?
wxz
 
Posts: 14
Joined: 23 Feb 2014, 06:14

Re: Quick and dirty client application for windows

Postby wxz » 19 Aug 2014, 23:16

ok, tried out the code and it works.
Just a reminder, in order to compile, do NOT include "winsock.h", "windows.h" already includes "winsock2.h". Include "winsock.h" after "winsock2.h" causes issues.

Also, add the line:
#pragma comment (lib, "ws2_32.lib")

for linkage.

About the heartbeat, it only requires once a second as done in the SDK.
wxz
 
Posts: 14
Joined: 23 Feb 2014, 06:14

Re: Quick and dirty client application for windows

Postby SimonH » 20 Aug 2014, 15:13

Great sample MastaLomasta and good advise wxz.

This helped me alot!

Did any of you figure out an easy way to integrate a calibration routine in c++ yet? I dont want to use the UI for it everytime, but i cant figure out a way on my own :(

Thx in advance!
SimonH
 
Posts: 4
Joined: 07 Aug 2014, 15:30

Re: Quick and dirty client application for windows

Postby wxz » 20 Aug 2014, 15:37

Developing calibration routine is more involved as it requires a UI framework (MFC, WPF, wxWidget etc.) to draw those dots on the screen one at a time.

For now, the easiest way is to use the calibration procedure provided by Eye Tribe. Once calibration is done, you can close UI. It's my impression that calibration is needed if Server restarts or the gaze data become deteriorated.
wxz
 
Posts: 14
Joined: 23 Feb 2014, 06:14

Re: Quick and dirty client application for windows

Postby jean.lorenceau » 27 Nov 2014, 15:42

Thanks a lot for this very simple code that avoids depending on the boost library and all other stuff.. I really appreciate having a direct hand on the EyeTribe with such a simple code.. I suggest that TheEyeTribe should advertise more these routines (perhaps providing a *.lib that includes these routines plus a calib routine) ... Would be great to extend the code so as to calibrate "from the inside" without depending upon the UI framework ...
In addition, a question: why is it necessary to call the "recv" routine twice (one with fix=0 and then with fix=1 in case the first call did not succeded ).. Also, what is the meaning of the "fix" variable which is poorly documented (the only information I could find is fix : // is fixated ?) ?

Thanks again !!
Best

Jean
jean.lorenceau
 
Posts: 4
Joined: 19 Feb 2014, 12:33

Re: Quick and dirty client application for windows

Postby Anders » 13 Dec 2014, 15:57

Fixation is basically keeping your visual fixation in the same place for a certain amount of time. Read more here. So the value of the API tells you if the gaze 'is fixated' at a certain area. This is usefull when dealing with selection and dwell time.

BR,
Anders
Anders
 
Posts: 124
Joined: 29 Oct 2013, 16:23

Re: Quick and dirty client application for windows

Postby MastaLomaster » 09 Feb 2015, 13:34

jean.lorenceau@upmc.fr wrote:why is it necessary to call the "recv" routine twice (one with fix=0 and then with fix=1 in case the first call did not succeded )..


It is not "recv", but "sscanf" function called twice.

The incoming data may contain "fix:true" or "fix:false" substring.
I would like to obtain this "true" or "false" value using the regular "sscanf" function. But I didn't manage. Templates like "fix:%s" didn't work for me.

So I prepared two templates (char *JSON_frame_true, *JSON_frame_false)
The only difference between them is "fix:true" or "fix:false" somewhere in the middle.

First I try to sscanf using JSON_frame_false. If it succeeds (returns 20 scanned values), then the fix variable remains 0;
If it doesn't - the fix variable is changed to 1 and we try to sscanf once again with the template containing "fix:true".

If you know the way to scanf strings like "fix:%s" I would appreciate.
(P.S. remember, it was "quick and dirty" code).
MastaLomaster
 
Posts: 37
Joined: 03 Mar 2014, 16:35

Re: Quick and dirty client application for windows

Postby Cheveoner » 26 Feb 2015, 15:23

Thanks for this client, it works perfectly until I start making things with it heh

My problem is that when I press enter, everything I want to do inside ReaderThread after the loop and before the return (save some fixation maps), is not executed. I tried changing the timeout of WaitForSingleObject to INFINTE (just in case the thread is not alllowed to finish properly), but I couldn't make it work.

Any suggestions?

Thanks in advance!
Cheveoner
 
Posts: 8
Joined: 26 Feb 2015, 15:07

Next

Return to C++



cron