view FXAnalyse.c @ 268:ec4462c7f8b7

Extensive cleanup of beatnote specific variables Reorganize the beatnote specific variables in arrays indexed by the beatnote enum constants LO, HG, SR. Also reorganize DDS frequency related variables in arrays indexed by the DDS channel number.
author Daniele Nicolodi <daniele.nicolodi@obspm.fr>
date Thu, 09 Jul 2015 23:11:00 +0200
parents 1de805d2d37a
children 3f395eab72eb
line wrap: on
line source

#include <zmq.h>
#include <tcpsupp.h>
#include <utility.h>
#include <ansi_c.h>
#include <lowlvlio.h>
#include <cvirte.h>		
#include <userint.h>
#include <formatio.h>
#include <inifile.h>
#include <string.h>

#include "FXAnalyse.h" 
#include "Plot.h"
#include "Allan.h"
#include "ad9912.h"
#include "ad9956.h" 
#include "muParserDLL.h"
#include "utils.h"
#include "stat.h"
#include "future.h"
#include "data-provider.h"
#include "sr-data-logger.h"
#include "config.h"
#include "logging.h"


#define FREP_STEP_SIZE 50000.0

#define SPEED_OF_LIGHT 299792458.0 // m/s

#define SR_FREQUENCY   429.228293 // THz
#define SR_WAVELENGTH  (SPEED_OF_LIGHT / SR_FREQUENCY / 1.0e3) // nm

#define HG_FREQUENCY   282.143622 // THz
#define HG_WAVELENGTH  (SPEED_OF_LIGHT / HG_FREQUENCY / 1.0e3) // nm


// select which data provider to use
#ifdef NDEBUG
#define DataProvider KKDataProvider
#else
#define DataProvider FakeDataProvider
#endif


// data acquisition status
int acquiring;
// data queue
CmtTSQHandle dataQueue;
// data provider thread
CmtThreadFunctionID dataProviderThread;

// ZMQ
void *zmqcontext;
void *zmqsocket;

// utility function to send data through ZMQ socket framed by an envelope
// see "Pub-Sub Message Envelopes" in chapter 2 "Sockets and Patterns" 
// of "ZMQ The Guide" http://zguide.zeromq.org/page:all#toc49
int zmq_xpub(void *socket, char *envelope, void *data, size_t len)
{
	int r;
	
	r = zmq_send(socket, envelope, strlen(envelope), ZMQ_SNDMORE);
	if (r < 0)
		return zmq_errno();
	
	r = zmq_send(socket, data, len, 0);
	if (r < 0)
		return zmq_errno();

	return 0;
}
	

struct event ev;
double utc;
#define Ch1 ev.data[0]
#define Ch2 ev.data[1]
#define Ch3 ev.data[2]
#define Ch4 ev.data[3]
double Math1, Math2, Math3, Math4, Math5;
double N1, N2, N3;
double Ndiv = 8.0;
double Sign1 = 1, Sign2 = 1, Sign3 = 1;
void *MathParser1, *MathParser2, *MathParser3, *MathParser4, *MathParser5;


// panels
static int MainPanel;
static int CalcNPanel;
static int EstimateNPanel;


struct adev {
	Allan_Data allan;
	double *data;
	const char *title;
	double normalization;
	int control;
};


#define ADEV(__channel, __normalization)		\
	{											\
		.data = & ## __channel,					\
		.title = "Adev " #__channel,		    \
		.normalization = __normalization,		\
		.control = PANEL_ADEV_ ## __channel		\
	}


static int adev_toggle(struct adev *adev)
{
	if (adev->allan.active)
		Allan_ClosePanel(&(adev->allan));
	else
		Allan_InitPanel(&(adev->allan), adev->title,
			adev->normalization, MainPanel, adev->control);
	return adev->allan.active;
}


static inline void adev_update(struct adev *adev)
{
	if (adev->allan.active)
		Allan_AddFrequency(&(adev->allan), *(adev->data));
}


struct adev adevs[] = {
	ADEV(Ch1, 1.80e12),
	ADEV(Ch2, 282.143e12),
	ADEV(Ch3, 429.228e12),
	ADEV(Ch4, 275.0e3),
	ADEV(Math1, 250.0e6),
	ADEV(Math2, 194.400e12),
	ADEV(Math3, 282.143e12),
	ADEV(Math4, 429.228e12),
	ADEV(Math5, 1.0),
	{ NULL }
};


struct plot {
	Plot_Data plot;
	double *data;
	const char *title;
	double min;
	double max;
	int control;
};


#define PLOT(__channel, __min, __max) 			\
	{											\
		.data = & ## __channel, 				\
		.title = "Plot " #__channel,			\
		.min = __min,							\
		.max = __max,							\
		.control = PANEL_PLOT_ ## __channel		\
	}


static int plot_toggle(struct plot *plot)
{
	if (plot->plot.active)
		Plot_ClosePanel(&(plot->plot));
	else
		Plot_InitPanel(&(plot->plot), plot->title,
			plot->min, plot->max, MainPanel, plot->control);
	return plot->plot.active;
}


static inline void plot_update(struct plot *plot)
{
	if (plot->plot.active)
		Plot_AddFrequency(&(plot->plot), *(plot->data));
}

struct plot plots[] = {
	PLOT(Ch1, 54.999e6, 55.001e6),
	PLOT(Ch2, 0.0, 0.0),
	PLOT(Ch3, 0.0, 0.0),
	PLOT(Ch4, 0.0, 0.0),
	PLOT(Math1, 0.0, 0.0),
	PLOT(Math2, 0.0, 0.0),
	PLOT(Math3, 0.0, 0.0),
	PLOT(Math4, 0.0, 0.0),
	PLOT(Math5, 0.0, 0.0),
	{ NULL }
};


struct ad9956 ad9956;
struct ad9912 ad9912;


static inline int ad9912_set_frequency_w(struct ad9912 *d, unsigned c, double f)
{
	int r = ad9912_set_frequency(d, c, f);
	if (r)
		logmessage(ERROR, "ad9912 set frequency channel=%d error=%d", c, -r);
	return r;
}


static inline int ad9912_ramp_frequency_w(struct ad9912 *d, unsigned c, double f, double s)
{
	int r = ad9912_ramp_frequency(d, c, f, s);
	if (r)
		logmessage(ERROR, "ad9912 ramp frequency channel=%d error=%d", c, -r);
	return r;
}


static inline int ad9956_set_sweep_rate_w(struct ad9956 *d, double s)
{
	int r = ad9956_set_sweep_rate(d, s);
	if (r)
		logmessage(ERROR, "ad9956 set sweep rate error=%d", -r);
	return r;
}


static int ad9956_set_w(struct ad9956 *d, double f, double s)
{
	int r;
	
	r = ad9956_sweep_stop(d);
	if (r) {
		logmessage(ERROR, "ad9956 sweep stop error=%d", -r);
		return r;
	}
	
	r = ad9956_set_frequency(d, f);
	if (r) {
		logmessage(ERROR, "ad9956 set frequency error=%d", -r);
		return r;
	}
	
	r = ad9956_set_sweep_rate(d, s);
	if (r) {
		logmessage(ERROR, "ad9956 set sweep rate error=%d", -r);
		return r;
	}
	
	r = ad9956_sweep_start(d);
	if (r) {
		logmessage(ERROR, "ad9956 sweep start error=%d", -r);
		return r;
	}

	return 0;	
}


enum {
	NONE = -1,
	LO = 0,
	MICROWAVE = LO,
	HG,
	SR,
	NBEATNOTES,
};


enum {
	N_MEASUREMENT_NONE,
	N_MEASUREMENT_INIT,
	N_MEASUREMENT_SLOPE,
	N_MEASUREMENT_ADJUST_FREQ_PLUS,
	N_MEASUREMENT_FREP_PLUS,
	N_MEASUREMENT_ADJUST_FREQ_MINUS,
	N_MEASUREMENT_FREP_MINUS,
};

int n_measurement[NBEATNOTES] = { N_MEASUREMENT_NONE, };
double slop_time[NBEATNOTES] = { 40.0, };
double integration_time[NBEATNOTES] = { 40.0, };
double delta_f_lock[NBEATNOTES] = { 500e3, };

double t1, t2, t3;
double f_rep_slope, f_beat_slope;
double f_rep_plus, f_rep_minus;
double f_beat_plus, f_beat_minus;
	
double f0_DDS[4] = { 110000000.0, 0.0, 0.0, 0.0 };
double df_DDS3 = 0.0;
	
int nobs = 0;
int settling = 0;


// Beatnote sign determination is done stepping the repetition rate by
// stepping the comb phase-lock offset frequency f_lock generated by
// DDS1.  A lock frequency step delta_f_lock determines a change in the
// repetition rate given by:
//
//   abs(delta_f_rep) = Ndiv * delta_f_lock / N1
// 
// where Ndiv = 8 and N1 ~= 8 x 10^5 obtaining that
// 
//   abs(delta_f_rep) ~= delta_f_lock / 10^5
//
// For the determination of the comb locking beatnote sign we detect
// the sign of delta_f_rep caused by a positive delta_f_lock. f_rep is
// measured should be small enough to do not exceed the 200x PLL
// bandwidth but still be clearly identified.
// 
// For the optical beatnotes we detect the sign of delta_f_beat caused
// by a positive delta_f_lock thus we need to take into account the
// sign of the comb locking beatnote.  The optical beatnote frequency
// change is given by
// 
//   abs(delta_f_beat) = abs(delta_f_rep) * Nx
// 
// where Nx ~= 10^6 obtaining that
// 
//   abs(delta_f_beat) ~= delta_f_lock * 10
//
// this need to do not exceed the beatnote filters bandwidth.  Given
// those contraints the following f_lock steps are chosen:

double f_lock_step[NBEATNOTES] = {
	[LO] = 10000.0,
	[HG] = 10.0,
	[SR] = 10.0
};

struct beatsign {
	int measure;		// which beatnote sign is being measured
	double f0_DDS;		// DDS frequency before stepping
	double t0;			// measurement start time
	double f_rep_zero;	// repetition rate before stepping
	double f_beat_zero;	// beatnote frequwncy before stepping
};

struct beatsign beatsign = {
	.measure = NONE,
};


struct stat stat_math1, stat_ch2, stat_ch3;
struct rollmean rollmean_ch1, rollmean_ch2, rollmean_ch3, rollmean_ch4;


// dedrift
struct dedrift {
	int enabled;        // dedrift enabled
	int proportional;   // enable proportional correction
	int reference;      // reference frequency
	int sign;           // sign of the correction
	int x2;    			// double the applied correction
	int keep_freq;      // keep current frequency value when dedrift is disabled
	int keep_slope;     // keep current slope value when dedrift is disabled
	double f0;          // target frequency
	double fDDS;        // DDS center frequency
	double applied;     // currently applied slope
	double interval;    // measurement duration
	double t0;          // beginning of currrent measurement interval
	double threshold;   // maximum allowed frequency change
	int badcount;       // number of bad data points encountered consecutively
	int badcountmax;    // maximum number of consecutive bad data points
	int safety;         // stop slope update when too many consecutive bad data points are detected
	struct stat stat;   // frequency mean and slope
};

struct dedrift dedrift = {
	.enabled = FALSE,
	.proportional = FALSE,
	.reference = MICROWAVE,
	.sign = +1,
	.x2 = FALSE,
	.keep_freq = TRUE,
	.keep_slope = TRUE,
	.f0 = 0.0,
	.fDDS = 70e6,
	.applied = 0.0,
	.interval = 30.0,
	.t0 = 0.0,
	.threshold = 20.0, // corresponding to a relative frequnecy stability of ~1e-13
	.badcount = 0,
	.badcountmax = 10,
	.safety = TRUE,
};


void dedrift_update_enable()
{
	logmsg("dedrift: automatic slope update enabled");
	
	dedrift.enabled = TRUE;
	dedrift.t0 = utc;
	stat_zero(&dedrift.stat);
	dedrift.stat.previous = NaN;
}


void dedrift_update_disable()
{
	logmsg("dedrift: automatic slope update disabled");

	dedrift.enabled = FALSE;
	stat_zero(&dedrift.stat);

	if (! dedrift.keep_slope) {
		dedrift.applied = 0.0;
		ad9956_set_sweep_rate_w(&ad9956, dedrift.applied);
	}
	if (! dedrift.keep_freq) {
		ad9956_set_w(&ad9956, dedrift.fDDS, dedrift.applied);
	}

	SetCtrlVal(MainPanel, PANEL_SLOPE_APPLIED, dedrift.applied);
	SetCtrlVal(MainPanel, PANEL_SLOPE_MEASURED, dedrift.stat.slope);
	SetCtrlVal(MainPanel, PANEL_MEASURE_SLOPE, 0);
}


// Wrapper around stat_accumulate() that updates the statistic only if
// the new datapoint `v` is within `threshold` from the data point
// considered in the previous update. If the data point fails this
// criteria it does not contribute to the collected statistics and the
// previous data point is used instead and `count` is incremented by
// one. If the data point is accepted `count` is reset to zero.
int stat_accumulate_resilient(struct stat *stat, double v, double threshold, int *count)
{
	if (!isnan(stat->previous) && (fabs(v - stat->previous) > threshold)) {
		// bad data point
		stat_accumulate(stat, stat->previous);
		*count += 1;
		return TRUE;
	} else {
		// good data point
		stat_accumulate(stat, v);
		*count = 0;
		return FALSE;
	}
}


void dedrift_update(double f)
{	
	if (! dedrift.enabled)
		return;

	// update measurement
	stat_accumulate_resilient(&dedrift.stat, f, dedrift.threshold, &dedrift.badcount);
	if (dedrift.badcount) {
		// bad data point detected
		logmsg("dedrift: bad data point detected");
		
		// too many consecutive bad data points detected
		if (dedrift.safety && (dedrift.badcount > dedrift.badcountmax)) {
			logmsg("dedrift: maximum number of consecutive bad data points exceeded");
			dedrift_update_disable();
		}
	}

	// check if the previous check disabled slope update
	if (! dedrift.enabled)
		return;
	
	// update display
	SetCtrlVal(MainPanel, PANEL_SLOPE_MEASURED, dedrift.stat.slope);
	
	// update applied slope
	if ((utc - dedrift.t0) > dedrift.interval) {
			
		// target frequency
		if (dedrift.f0 == 0.0)
			dedrift.f0 = dedrift.stat.mean;
		
		// compute correction
		double dt = utc - dedrift.t0;
		double corr = dedrift.stat.slope \
			+ dedrift.proportional * ((dedrift.stat.mean - dedrift.f0) / dt + 0.5 * dedrift.stat.slope);
		
		// update
		dedrift.applied += dedrift.sign * corr * (dedrift.x2 ? 2 : 1);
		ad9956_set_sweep_rate_w(&ad9956, dedrift.applied);
		SetCtrlVal(MainPanel, PANEL_SLOPE_APPLIED, dedrift.applied);
		logmsg("dedrift: update correction=%+3e slope=%+3e", corr, dedrift.applied);
		
		// start over. keep track of the last updated data point to
		// avoid gaps in the detectrion of bad data points based on
		// threshold on the difference between current data point and
		// previous one
		double prev = dedrift.stat.previous;
		stat_zero(&dedrift.stat);
		dedrift.stat.previous = prev;
		dedrift.t0 = utc;
	}
}


// recenter
struct recenter {
	int active;						// recenter enabled
	int enabled[NBEATNOTES];		// which beatnotes to recenter
	double threshold[NBEATNOTES];	// maximum frequency correction
	double interval;				// interval
	double t0;						// beginning of current interval
};

struct recenter recenter = {
	.active = FALSE,
	.enabled = { FALSE, FALSE, FALSE },
	.threshold = { 10.0, 2000.0, 2000.0 },
	.interval = 1800.0,
	.t0 = 0.0
};


int recenter_enabled()
{
	if (! recenter.active)
		return FALSE;
	
	for (int i = 0; i < NBEATNOTES; i++)
		if (recenter.enabled[i])
			return TRUE;

	return FALSE;
}


void recenter_update()
{
	if (! recenter_enabled())
		return;
	
	rollmean_accumulate(&rollmean_ch2, Ch2);
	rollmean_accumulate(&rollmean_ch3, Ch3);
	rollmean_accumulate(&rollmean_ch4, Ch4);
			
	if ((utc - recenter.t0) > recenter.interval) {
		
		if (recenter.enabled[LO]) {
			// adjust DDS2 frequency to keep Ch4 reading at 275 kHz
			double freq = ad9912.frequency[1];
			double adj = 275000.0 - rollmean_ch4.mean;
			if (fabs(adj) > recenter.threshold[LO]) {
				logmessage(WARNING, "not recenter ch4 to 275 kHz: DDS2 adjustment=%+3e exceeds threshold", adj);
			} else {
				freq = freq + adj;
				ad9912_set_frequency_w(&ad9912, 1, freq);
				SetCtrlVal(MainPanel, PANEL_DDS2, ad9912.frequency[1]);
				logmsg("recenter ch4 to 275 kHz: DDS2 adjustment=%+3e", adj);
			}
		}
				
		if (recenter.enabled[HG]) {
			// adjust DDS3 frequency to keep Ch2 reading at 10 kHz
			double freq = ad9912.frequency[2];
			double adj = 10000 - rollmean_ch2.mean;
			if (fabs(adj) > recenter.threshold[HG]) {
				logmessage(WARNING, "not recenter Hg beatnote (ch2) to 10 kHz: DDS3 adjustment=%+3e exceeds threshold", adj);
			} else {
				freq = freq + adj;
				ad9912_set_frequency_w(&ad9912, 2, freq);
				SetCtrlVal(MainPanel, PANEL_DDS3, ad9912.frequency[2]);
				logmsg("recenter Hg beatnote (ch2) to 10 kHz: DDS3 adjustment=%+3e", adj);
			}
		}
			
		if (recenter.enabled[SR]) {
			// adjust DDS4 frequency to keep Ch3 reading at 10 kHz
			double freq = ad9912.frequency[3];
			double adj = 10000 - rollmean_ch3.mean;
			if (fabs(adj) > recenter.threshold[SR]) {
				logmessage(WARNING, "not recenter Sr beatnote (ch3) to 10 kHz: DDS4 adjustment=%+3e exceeds threshold", adj);
			} else {
				freq = freq + adj;
				ad9912_set_frequency_w(&ad9912, 3, freq);
				SetCtrlVal(MainPanel, PANEL_DDS4, ad9912.frequency[3]);
				logmsg("recenter Sr beatnote (ch3) to 10 kHz: DDS4 adjustment=%+3e", adj);
			}
		}
				
		recenter.t0 = utc;
		rollmean_zero(&rollmean_ch2);
		rollmean_zero(&rollmean_ch3);
		rollmean_zero(&rollmean_ch4);
	}
}


// data loggging
static char *datafolder;

struct datafile {
	char *name;
	double *data;
	int nchan;
	int control;
	int write;
};

#define DATAFILE(__name, __data, __nchan, __control, __write)	\
	{															\
		.name = __name,											\
		.data = __data,											\
		.nchan = __nchan,										\
		.control = __control,									\
		.write = __write,										\
	}

struct datafile datafiles[] = {
	// set the counter channels number to zero. it will
	// be updated when the configuration file is read
	DATAFILE("Raw", ev.data, 0, PANEL_SAVE_RAW, TRUE),
	DATAFILE("DDS", ad9912.frequency, 4, PANEL_SAVE_DDS, FALSE),
	DATAFILE("Lo", &Math2, 1, PANEL_SAVE_LO, FALSE),
	DATAFILE("Hg", &Math3, 1, PANEL_SAVE_HG, FALSE),
	DATAFILE("Sr", &Math4, 1, PANEL_SAVE_SR, FALSE),
	DATAFILE("Ex", &Math5, 1, PANEL_SAVE_EXTRA, FALSE),
	{ NULL, }
};


static void write_data(const char *folder, const char *name, const char *id, 
		const char *timestr, double utc, double *v, int nchan)
{
	int i, fd, len;
	char line[1024];
	char filename[FILENAME_MAX];
	
	// construct filename in the form folder\\id-name.txt
	snprintf(filename, sizeof(filename), "%s\\%s-%s.txt", folder, id, name);
	
	fd = open(filename, O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP);
	if (fd < 0) {
		logmessage(ERROR, "open data file %s: %s", filename, strerror(errno));
		return;
	}
	
	// timestamp
	len = snprintf(line, sizeof(line), "%s\t%.3f", timestr, utc);

	// data channels
	for (i = 0; i < nchan; i++)
		len += snprintf(line + len, sizeof(line) - len, "\t%.16e", v[i]);
	
	// newline
	line[len++] = '\r';
	line[len++] = '\n';
			
	// write to file
	write(fd, line, len);
	
	close(fd);
}


static inline void datafile_append(struct datafile *d, char *id, char *timestr)
{
	if (d->write)
		write_data(datafolder, d->name, id, timestr, utc, d->data, d->nchan);
}


static struct datalogger datalogger;


static void onerror(int level, const char *msg)
{
	SetCtrlVal(MainPanel, PANEL_ERROR, 1);
}


static void * muParserNew()
{
	void *parser = mupCreate();
	
	mupDefineOprtChars(parser, "abcdefghijklmnopqrstuvwxyzµ"
                        	   "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                        	   "+-*^/?<>=#!$%&|~'_");
	
	mupDefineVar(parser, "Ch1", &Ch1);
	mupDefineVar(parser, "Ch2", &Ch2);
	mupDefineVar(parser, "Ch3", &Ch3);
	mupDefineVar(parser, "Ch4", &Ch4);
	mupDefineVar(parser, "DDS1", &(ad9912.frequency[0]));
	mupDefineVar(parser, "DDS2", &(ad9912.frequency[1]));
	mupDefineVar(parser, "DDS3", &(ad9912.frequency[2]));
	mupDefineVar(parser, "DDS4", &(ad9912.frequency[3]));
	mupDefineVar(parser, "N1", &N1);
	mupDefineVar(parser, "N2", &N2);
	mupDefineVar(parser, "N3", &N3);
	mupDefineVar(parser, "Sign1", &Sign1);
	mupDefineVar(parser, "Sign2", &Sign2);
	mupDefineVar(parser, "Sign3", &Sign3);
	mupDefineVar(parser, "Ndiv", &Ndiv);
	
	mupDefinePostfixOprt(parser, "P", &Peta,  1);
	mupDefinePostfixOprt(parser, "T", &Tera,  1);
	mupDefinePostfixOprt(parser, "G", &Giga,  1);
	mupDefinePostfixOprt(parser, "M", &Mega,  1);
	mupDefinePostfixOprt(parser, "k", &kilo,  1);
	mupDefinePostfixOprt(parser, "m", &milli, 1);
	mupDefinePostfixOprt(parser, "u", &micro, 1);
	mupDefinePostfixOprt(parser, "µ", &micro, 1);
	mupDefinePostfixOprt(parser, "n", &nano,  1);
	mupDefinePostfixOprt(parser, "p", &pico,  1);
	mupDefinePostfixOprt(parser, "f", &femto, 1);
	
	return parser;
}


void CVICALLBACK DataAvailableCB (CmtTSQHandle queueHandle, unsigned int event,
		int value, void *callbackData);


int main (int argc, char *argv[])
{
	int i, rv, nchan;
	double frequency, clock;
	char expr[1024];
	char host[256];
	int PANEL_DDS[4] = { PANEL_DDS1, PANEL_DDS2, PANEL_DDS3, PANEL_DDS4 };

	if ((MainPanel = LoadPanel (0, "FXAnalyse.uir", PANEL)) < 0)
		return -1;
	if ((CalcNPanel = LoadPanel (MainPanel, "FXAnalyse.uir", CALCN)) < 0)
		return -1;
	if ((EstimateNPanel = LoadPanel (MainPanel, "FXAnalyse.uir", ESTIMATEN)) < 0)
		return -1;

	// logging
	logger_init(&onerror);
	
	// load configuration file
	char path[MAX_PATHNAME_LEN];
	GetIniFilePath(path);
	IniText configuration = Ini_New(TRUE);
	Ini_ReadFromFile(configuration, path);

	// KK counter channel number
	rv = Ini_GetInt(configuration, "KK", "nchan", &nchan);
	if (rv < 1)
		nchan = 4;
	// update number of channels to save to disk
	datafiles[0].nchan = nchan;
	
	// data folder
	rv = Ini_GetStringCopy(configuration, "data", "folder", &datafolder);
	if (rv > 0) {
		logmessage(INFO, "writing data files in '%s'", datafolder);
	} else {
		logmessage(ERROR, "data folder not configured in %s", path);
		// do not allow to start the acquisition
		SetCtrlAttribute(MainPanel, PANEL_STARTBUTTON, ATTR_DIMMED, TRUE);
	}
	
	// ad9956 configuration parameters
	rv = Ini_GetStringIntoBuffer(configuration, "AD9956", "host", host, sizeof(host));
	if (! rv)
		return -1;
	rv = Ini_GetDouble(configuration, "AD9956", "clock", &clock);
	if (! rv)
		return -1;
	
	// initialize AD9956 dedrift DDS
	rv = ad9956_init(&ad9956, host, clock);
	if (rv)
		logmessage(ERROR, "ad9956 init erorr=%d", -rv);
	ad9956_set_w(&ad9956, dedrift.fDDS, dedrift.applied);
	
	// AD9912 configuration parameters
	rv = Ini_GetStringIntoBuffer(configuration, "AD9912", "host", host, sizeof(host));
	if (! rv)
		return -1;
	rv = Ini_GetDouble(configuration, "AD9912", "clock", &clock);
	if (! rv)
		return -1;
	
	// initialize AD9912 DDS box
	rv = ad9912_init(&ad9912, host, clock);
	if (rv)
		logmessage(ERROR, "ad9912 init erorr=%d", -rv);
	
	// try to read back current frequency from DDS
	for (i = 0; i < 4; i++) {
		rv = ad9912_get_frequency(&ad9912, i, &frequency);
		if ((rv) || (frequency == 0.0)) {
			logmessage(WARNING, "reset DDS%d frequency to default value", i + 1);
			GetCtrlVal(MainPanel, PANEL_DDS[i], &frequency);
			ad9912_set_frequency_w(&ad9912, i, frequency);
		}
		SetCtrlVal(MainPanel, PANEL_DDS[i], frequency);
	}

	// setup ZMQ pub socket
	char *socket;
	rv = Ini_GetStringCopy(configuration, "ZMQ", "socket", &socket);
	if (! rv)
		socket = strdup("tcp://127.0.0.1:3456");
	logmessage(INFO, "data sent to ZMQ socket '%s'", socket);
	zmqcontext = zmq_ctx_new();
	zmqsocket = zmq_socket(zmqcontext, ZMQ_PUB);
	rv = zmq_bind(zmqsocket, socket);
	if (rv)
		logmessage(ERROR, "cannot bind ZMQ socket '%s': %s", socket, zmq_strerror(zmq_errno()));
	free(socket);
	
	// dispose configuration
	Ini_Dispose(configuration);

	// Sr data logger
	sr_datalogger_init(&datalogger);
	
	GetCtrlVal(MainPanel, PANEL_N1, &N1);
	GetCtrlVal(MainPanel, PANEL_N2, &N2);
	GetCtrlVal(MainPanel, PANEL_N3, &N3);

	MathParser1 = muParserNew();
	GetCtrlVal(MainPanel, PANEL_MATHSTRING1, expr);
	mupSetExpr(MathParser1, expr);
	
	MathParser2 = muParserNew();
	mupDefineVar(MathParser2, "Math1", &Math1);
	GetCtrlVal(MainPanel, PANEL_MATHSTRING2, expr);
	mupSetExpr(MathParser2, expr);
	
	MathParser3 = muParserNew();
	mupDefineVar(MathParser3, "Math1", &Math1);
	mupDefineVar(MathParser3, "Math2", &Math2);
	GetCtrlVal(MainPanel, PANEL_MATHSTRING3, expr);
	mupSetExpr(MathParser3, expr);
	
	MathParser4 = muParserNew();
	mupDefineVar(MathParser4, "Math1", &Math1);
	mupDefineVar(MathParser4, "Math2", &Math2);
	mupDefineVar(MathParser4, "Math3", &Math3);
	GetCtrlVal(MainPanel, PANEL_MATHSTRING4, expr);
	mupSetExpr(MathParser4, expr);
	
	MathParser5 = muParserNew();
	mupDefineVar(MathParser5, "Math1", &Math1);
	mupDefineVar(MathParser5, "Math2", &Math2);
	mupDefineVar(MathParser5, "Math3", &Math3);
	mupDefineVar(MathParser5, "Math4", &Math4);
	GetCtrlVal(MainPanel, PANEL_MATHSTRING5, expr);
	mupSetExpr(MathParser5, expr);
	
	// data queue
	CmtNewTSQ(128, sizeof(struct event), 0, &dataQueue);

	// register callback to execute when data will be in the data queue
	CmtInstallTSQCallback(dataQueue, EVENT_TSQ_ITEMS_IN_QUEUE, 1,
		DataAvailableCB, NULL, CmtGetCurrentThreadID(), NULL);
	
	DisplayPanel(MainPanel);
	
	RunUserInterface();
	
	DiscardPanel(MainPanel);
	return 0;
}

int CVICALLBACK QuitCallback (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			QuitUserInterface(0);
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnEventMain(int panel, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int control, index;
	double step;
	
#define do_arrow(__DDS, __STEP)										\
	do {															\
		GetCtrlIndex(panel, __STEP, &index);						\
		if ((eventData1 == VAL_RIGHT_ARROW_VKEY) && (index < 14))	\
			SetCtrlIndex(panel, __STEP, index + 1);					\
		if ((eventData1 == VAL_LEFT_ARROW_VKEY) && (index > 0))		\
			SetCtrlIndex(panel, __STEP, index - 1);					\
		GetCtrlVal(panel, __STEP, &step);							\
		SetCtrlAttribute(panel, __DDS, ATTR_INCR_VALUE, step);  	\
	} while (0) 
	
	switch (event) {
		case EVENT_KEYPRESS:
			/* key code */
			switch (eventData1) {
				case VAL_RIGHT_ARROW_VKEY:
				case VAL_LEFT_ARROW_VKEY:					
					control = GetActiveCtrl(panel);
					switch (control) {
						case PANEL_DDS1:
						case PANEL_DDS1STEP:
							do_arrow(PANEL_DDS1, PANEL_DDS1STEP);
							break;
						case PANEL_DDS2:
						case PANEL_DDS2STEP:
							do_arrow(PANEL_DDS2, PANEL_DDS2STEP);
							break;
						case PANEL_DDS3:
						case PANEL_DDS3STEP:
							do_arrow(PANEL_DDS3, PANEL_DDS3STEP);
							break;
						case PANEL_DDS4:
						case PANEL_DDS4STEP:
							do_arrow(PANEL_DDS4, PANEL_DDS4STEP);
							break;
					}
					break;
				case VAL_F2_VKEY :
					SetActiveCtrl(panel, PANEL_DDS1);
					break;
				case VAL_F3_VKEY :
					SetActiveCtrl(panel, PANEL_DDS2);
					break;
				case VAL_F4_VKEY :
					SetActiveCtrl(panel, PANEL_DDS3);
					break;
				case VAL_F5_VKEY :
					SetActiveCtrl(panel, PANEL_DDS4);
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnStart (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			if (acquiring)
				break;

			logmsg("start");
			SetCtrlAttribute(panel, PANEL_STARTBUTTON, ATTR_DIMMED, TRUE);
			acquiring = 1;
			
			// start data provider thread
			CmtScheduleThreadPoolFunctionAdv(
				DEFAULT_THREAD_POOL_HANDLE, DataProvider, NULL,
				THREAD_PRIORITY_HIGHEST, NULL, 0, NULL, 0, &dataProviderThread);

			break;
	}
	return 0;
}

int CVICALLBACK CB_OnStop (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event) {
		case EVENT_COMMIT:
			if (! acquiring)
				break;
			
			logmsg("stop");
			acquiring = 0;

			// wait for data provider thread to terminate
			CmtWaitForThreadPoolFunctionCompletion(
				DEFAULT_THREAD_POOL_HANDLE, dataProviderThread,
				OPT_TP_PROCESS_EVENTS_WHILE_WAITING);
			CmtReleaseThreadPoolFunctionID(
				DEFAULT_THREAD_POOL_HANDLE, dataProviderThread);

			SetCtrlAttribute(panel, PANEL_STARTBUTTON, ATTR_DIMMED, FALSE);
			
			break;
	}
	return 0;
}

void CVICALLBACK DataAvailableCB (CmtTSQHandle queueHandle, unsigned int event,
		int value, void *callbackData)
{
	int read;
	
	switch (event) {
		case EVENT_TSQ_ITEMS_IN_QUEUE:
			// read data from the data queue
			while (value > 0) {
				
				read = CmtReadTSQData(queueHandle, &ev, 1, TSQ_INFINITE_TIMEOUT, 0);
				if (read != 1)
					logmsg("Error!");
				value = value - read;
				
				// unpack event
				utc = ev.time.tv_sec + ev.time.tv_usec * 1e-6;

				// update display
				SetCtrlVal(MainPanel, PANEL_UTC, utc);
				SetCtrlVal(MainPanel, PANEL_CH1, Ch1);
				SetCtrlVal(MainPanel, PANEL_CH2, Ch2);
				SetCtrlVal(MainPanel, PANEL_CH3, Ch3);  
				SetCtrlVal(MainPanel, PANEL_CH4, Ch4);

				// compute
				Math1 = mupEval(MathParser1);
				Math2 = mupEval(MathParser2);
				Math3 = mupEval(MathParser3);
				Math4 = mupEval(MathParser4);
				Math5 = mupEval(MathParser5);
				
				// update display.  numeric controllers do not format values
				// with a thousands separator: use string controllers and a 
				// custom formatting function
				char buffer[256];
				SetCtrlVal(MainPanel, PANEL_MATH1, thousands(buffer, sizeof(buffer), "%.6f", Math1));
				SetCtrlVal(MainPanel, PANEL_MATH2, thousands(buffer, sizeof(buffer), "%.3f", Math2));
				SetCtrlVal(MainPanel, PANEL_MATH3, thousands(buffer, sizeof(buffer), "%.3f", Math3));
				SetCtrlVal(MainPanel, PANEL_MATH4, thousands(buffer, sizeof(buffer), "%.3f", Math4));
				SetCtrlVal(MainPanel, PANEL_MATH5, thousands(buffer, sizeof(buffer), "%.3f", Math5));

				// update timeseries plots
				for (struct plot *plot = plots; plot->data; plot++)
					plot_update(plot);

				// update allan deviation plots
				for (struct adev *adev = adevs; adev->data; adev++)
					adev_update(adev);
				
				// N measurement

				switch (n_measurement[LO]) {
					
					case N_MEASUREMENT_NONE:
						// not measuring
						break;
					
					case N_MEASUREMENT_INIT:
						// initialization step
						
						// set DDS1 to nominal frequency
						ad9912_set_frequency_w(&ad9912, 0, f0_DDS[0]);
						SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
						
						// record current DDS frequencies
						f0_DDS[1] = ad9912.frequency[1];
						
						t1 = utc;
						t2 = t3 = 0.0;
						nobs = 0;
						stat_zero(&stat_math1);
						f_rep_plus = f_rep_minus = 0.0;
						
						// next step
						n_measurement[LO] += 1;
						break;
						
					case N_MEASUREMENT_SLOPE:
						// slope measurement
						
						stat_accumulate(&stat_math1, Math1);
						
						if ((utc - t1) > slope_time[LO]) {
							f_rep_slope = stat_math1.slope;
							
							// frep positive step
							ad9912_ramp_frequency_w(&ad9912, 0, f0_DDS[0] + delta_f_lock[LO], FREP_STEP_SIZE);
							SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
							
							// allow counter to settle
							settling = 3;
							
							// next step
							n_measurement[LO] += 1;
						}
						break;
						
					case N_MEASUREMENT_ADJUST_FREQ_PLUS:
					case N_MEASUREMENT_ADJUST_FREQ_MINUS:
						// adjust DDS frequency to keep beatnote within the bandpass filter
						
						if (settling-- > 0)
							break;
						
						double fDDS2 = ad9912.frequency[1];
						ad9912_set_frequency_w(&ad9912, 1, fDDS2 + 275000 - Ch4);
						SetCtrlVal(MainPanel, PANEL_DDS2, ad9912.frequency[1]);
						
						// allow counter to settle
						settling = 3;
						
						// next step
						n_measurement[LO] += 1;
						break;								
						
					case N_MEASUREMENT_FREP_PLUS:
						// frep positive step
						
						if (settling-- > 0)
							break;
						
						if (t2 == 0.0)
							t2 = utc;
						
						f_rep_plus += Math1 - f_rep_slope * (utc - t2);
						nobs += 1;
						
						if ((utc - t2) > integration_time[LO]) {
							f_rep_plus = f_rep_plus / nobs;
							nobs = 0;
							
							// frep negative step
							ad9912_ramp_frequency_w(&ad9912, 0, f0_DDS[0] - delta_f_lock[LO], FREP_STEP_SIZE);
							SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
							
							// allow counter to settle
							settling = 3;
							
							// next step
							n_measurement[LO] += 1;
						}
						break;
						
					case N_MEASUREMENT_FREP_MINUS:
						// frep negative step
						
						if (settling-- > 0)
							break;
						
						if (t3 == 0.0)
							t3 = utc;
						
						f_rep_minus += Math1 - f_rep_slope * (utc - t2);
						nobs += 1;
						
						if ((utc - t3) > integration_time[LO]) {
							f_rep_minus = f_rep_minus / nobs;
							nobs = 0;
							
							// compute N1
							double delta_f_rep = f_rep_minus - f_rep_plus;
							double measured = Sign1 * 2 * Ndiv * delta_f_lock[LO] / delta_f_rep;
							SetCtrlVal(CalcNPanel, CALCN_N, measured);
							
							// back to nominal frep
							ad9912_ramp_frequency_w(&ad9912, 0, f0_DDS[0], FREP_STEP_SIZE);
							ad9912_set_frequency_w(&ad9912, 1, f0_DDS[1]);
							SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
							SetCtrlVal(MainPanel, PANEL_DDS2, ad9912.frequency[1]);
							
							// done
							n_measurement[LO] = N_MEASUREMENT_NONE;
						}
						break;
				}

				switch (n_measurement[HG]) {

					case N_MEASUREMENT_NONE:
						// not measuring
						break;
					
					case N_MEASUREMENT_INIT:
						// initialization step
						
						// set DDS1 to nominal frequency
						ad9912_set_frequency_w(&ad9912, 0, f0_DDS[0]);
						SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
						
						// record current DDS frequencies
						f0_DDS[1] = ad9912.frequency[1];
						f0_DDS[2] = ad9912.frequency[2];
						
						t1 = utc;
						t2 = t3 = 0.0;
						nobs = 0;
						stat_zero(&stat_math1);
						stat_zero(&stat_ch2);
						f_rep_plus = f_rep_minus = 0.0;
						f_beat_plus = f_beat_minus = 0.0;
						
						// next step
						n_measurement[HG] += 1;
						break;
						
					case N_MEASUREMENT_SLOPE:
						// slope measurement
						
						stat_accumulate(&stat_math1, Math1);
						stat_accumulate(&stat_ch2, Ch2);

						if ((utc - t1) > slope_time[HG]) {
							f_rep_slope = stat_math1.slope;
							f_beat_slope = stat_ch2.slope;
							
							// frep positive step
							double fDDS1 = f0_DDS[0] + delta_f_lock[HG];
							ad9912_ramp_frequency_w(&ad9912, 0, fDDS1, FREP_STEP_SIZE);
							SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
							
							// adjust DDS3 to keep beatnote within the bandpass filter. prediction
							double fDDS3 = f0_DDS[2] + Sign1 * Sign2 * N2/N1 * Ndiv * delta_f_lock[HG];
							df_DDS3 = fDDS3 - ad9912.frequency[2];
							ad9912_set_frequency_w(&ad9912, 2, fDDS3);
							SetCtrlVal(MainPanel, PANEL_DDS3, ad9912.frequency[2]);
						
							// allow counter to settle
							settling = 3;
							
							// next step
							n_measurement[HG] += 1;
						}
						break;

					case N_MEASUREMENT_ADJUST_FREQ_PLUS:
					case N_MEASUREMENT_ADJUST_FREQ_MINUS:
						// adjust DDS frequency to keep beatnote within the bandpass filter
						
						if (settling-- > 0)
							break;
						
						double fDDS2 = ad9912.frequency[1] + 275000 - Ch4;
						ad9912_set_frequency_w(&ad9912, 1, fDDS2);
						SetCtrlVal(MainPanel, PANEL_DDS2, ad9912.frequency[1]);
						
						double fDDS3 = ad9912.frequency[2] + 10000 - Ch2;
						df_DDS3 = df_DDS3 + 10000 - Ch2;
						ad9912_set_frequency_w(&ad9912, 2, fDDS3);
						SetCtrlVal(MainPanel, PANEL_DDS3, ad9912.frequency[2]);

						// allow counter to settle
						settling = 3;

						// next step
						n_measurement[HG] += 1;
						break;
						
					case N_MEASUREMENT_FREP_PLUS:
						// frep positive step
						
						if (settling-- > 0)
							break;

						if (t2 == 0.0)
							t2 = utc;

						f_rep_plus += Math1 + 250000000 - f_rep_slope * (utc - t2);
						f_beat_plus += Ch2 - f_beat_slope * (utc - t2);
						nobs += 1;
						
						if ((utc - t2) > integration_time[HG]) {
							f_rep_plus = f_rep_plus / nobs;
							f_beat_plus = f_beat_plus / nobs;
							nobs = 0;

							// negative frequency step
							double fDDS1 = f0_DDS[0] - delta_f_lock[HG];
							ad9912_ramp_frequency_w(&ad9912, 0, fDDS1, FREP_STEP_SIZE);
							SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
							
							// adjust DDS3 to keep beatnote within the bandpass filter. prediction
							double fDDS3 = f0_DDS[2] - Sign1 * Sign2 * N2/N1 * Ndiv * delta_f_lock[HG];
							df_DDS3 = fDDS3 - ad9912.frequency[2];
							ad9912_set_frequency_w(&ad9912, 2, fDDS3);
							SetCtrlVal(MainPanel, PANEL_DDS3, ad9912.frequency[2]);

							// allow counter to settle
							settling = 3;
							
							// next step
							n_measurement[HG] += 1;
						}
						break;
						
					case N_MEASUREMENT_FREP_MINUS:
						// frep negative step
						
						if (settling-- > 0)
							break;
						
						if (t3 == 0.0)
							t3 = utc;

						f_rep_minus += Math1 + 250000000 - f_rep_slope * (utc - t2);
						f_beat_minus += Ch2 + f_beat_slope * (utc - t2);
						nobs += 1;

						if ((utc -t3) > integration_time[HG]) {
							f_rep_minus = f_rep_minus / nobs;
							f_beat_minus = f_beat_minus / nobs;
							nobs = 0;

							// compute N2
							double delta_f_rep_m = f_rep_minus - f_rep_plus;
							double delta_f_rep = Sign1 * Ndiv * 2.0 * delta_f_lock[HG] / N1;
							double delta = delta_f_rep_m - delta_f_rep;
							
							logmsg("delta frep: measured=%e expected=%e difference=%e rel=%e",
								delta_f_rep_m, delta_f_rep, delta, delta / delta_f_rep);
							
							double measured = -Sign2 * (df_DDS3 + f_beat_minus - f_beat_plus) / delta_f_rep;
							SetCtrlVal(CalcNPanel, CALCN_N, measured);
							
							// back to nominal frequency
							ad9912_ramp_frequency_w(&ad9912, 0, f0_DDS[0], FREP_STEP_SIZE);
							ad9912_set_frequency_w(&ad9912, 1, f0_DDS[1]);
							ad9912_set_frequency_w(&ad9912, 2, f0_DDS[2]);
							SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
							SetCtrlVal(MainPanel, PANEL_DDS2, ad9912.frequency[1]);
							SetCtrlVal(MainPanel, PANEL_DDS3, ad9912.frequency[2]);
							
							// done
							n_measurement[HG] = N_MEASUREMENT_NONE;
						}
						break;
				}
				
				switch (n_measurement[SR]) {
					
					case N_MEASUREMENT_NONE:
						// not measuring N3
						break;
						
					case N_MEASUREMENT_INIT:
						// init
						
						// set DDS1 to nominal frequency
						ad9912_set_frequency_w(&ad9912, 0, f0_DDS[0]);
						SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
						
						// record current DDS frequencies
						f0_DDS[1] = ad9912.frequency[1];
						f0_DDS[3] = ad9912.frequency[3];
						
						t1 = utc;
						t2 = t3 = 0.0;
						nobs = 0;
						stat_zero(&stat_math1);
						stat_zero(&stat_ch3);
						f_rep_plus = f_rep_minus = 0.0;
						f_beat_plus = f_beat_minus = 0.0;
						
						// next step
						n_measurement[SR] += 1;
						break;
						
					case N_MEASUREMENT_SLOPE:
						// slope measurement
						
						if (settling-- > 0)
							break;
						
						stat_accumulate(&stat_math1, Math1);
						stat_accumulate(&stat_ch3, Ch3);
						
						if (utc - t1 > slope_time[SR]) {
							// slope measurement
							f_rep_slope = stat_math1.slope;
							f_beat_slope = stat_ch3.slope;
							
							logmsg("f_rep_slope=%e Hz/s", f_rep_slope);
							logmsg("f_beat_slope=%e Hz/s", f_rep_slope);
							
							// frep positive step
							ad9912_ramp_frequency_w(&ad9912, 0, f0_DDS[0] + delta_f_lock[SR], FREP_STEP_SIZE);
							SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
							
							// adjust DDS3 to keep beatnote within the bandpass filter
							double fDDS4 = f0_DDS[3] + Sign1 * Sign3 * N3/N1 * Ndiv * delta_f_lock[SR];
							ad9912_set_frequency_w(&ad9912, 3, fDDS4);
							SetCtrlVal(MainPanel, PANEL_DDS4, ad9912.frequency[3]);
							
							// allow counter to settle
							settling = 3;
							
							// next step
							n_measurement[SR] += 1;
						}
						break;

					case N_MEASUREMENT_ADJUST_FREQ_PLUS:
					case N_MEASUREMENT_ADJUST_FREQ_MINUS:
						// adjust DDS frequency to keep beatnote within the bandpass filter

						if (settling-- > 0)
							break;
						
						// adjust DDS frequency to keep 55 MHz tracker oscillator locked
						double fDDS2 = ad9912.frequency[1] + 275000 - Ch4;
						ad9912_set_frequency_w(&ad9912, 1, fDDS2);
						SetCtrlVal(MainPanel, PANEL_DDS2, ad9912.frequency[1]);
						
						// allow counter to settle
						settling = 3;
						
						// next step
						n_measurement[SR] += 1;
						break;						
						
					case N_MEASUREMENT_FREP_PLUS:
						// frep positive step
						
						if (settling-- > 0)
							break;

						if (t2 == 0.0)
							t2 = utc;
						
						f_rep_plus += Math1 + 250000000 - f_rep_slope * (utc - t2);
						f_beat_plus += Ch3 - f_beat_slope * (utc - t2);
						nobs += 1;
						
						if (utc - t2 > integration_time[SR]) {
							f_rep_plus = f_rep_plus / nobs;
							f_beat_plus = f_beat_plus / nobs;
							nobs = 0;
							
							// frep negative step
							ad9912_ramp_frequency_w(&ad9912, 0, f0_DDS[0] - delta_f_lock[SR], FREP_STEP_SIZE);
							SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);

							// adjust DDS3 to keep beatnote within the bandpass filter
							double fDDS4 = f0_DDS[3] - Sign1 * Sign3 * N3/N1 * Ndiv * delta_f_lock[SR];
							ad9912_set_frequency_w(&ad9912, 3, fDDS4);
							SetCtrlVal(MainPanel, PANEL_DDS4, ad9912.frequency[3]);
							
							// allow counter to settle
							settling = 3;
							
							// next step
							n_measurement[SR] += 1;
						}
						break;
					
					case N_MEASUREMENT_FREP_MINUS:
						// frep negative step
						
						if (settling-- > 0)
							break;
						
						if (t3 == 0.0)
							t3 = utc;
						
						f_rep_minus += Math1 + 250000000 - f_rep_slope * (utc - t2);
						f_beat_minus += Ch3 - f_beat_slope * (utc - t2);
						nobs += 1;
						
						if (utc - t3 > integration_time[SR]) {
							f_rep_minus = f_rep_minus / nobs;
							f_beat_minus = f_beat_minus / nobs;
							nobs = 0;
							
							// check delta frep
							double delta_f_rep_m = f_rep_minus - f_rep_plus;
							double delta_f_rep = Sign1 * Ndiv * 2.0 * delta_f_lock[SR] / N1;
							double delta = delta_f_rep_m - delta_f_rep;
							
							logmsg("delta frep: measured=%e expected=%e difference=%e rel=%e",
								delta_f_rep_m, delta_f_rep, delta, delta / delta_f_rep);
							
							// compute N3
							double delta_f_beat = f_beat_minus - f_beat_plus + 2.0 * Sign1 * Sign3 * N3/N1 * Ndiv * delta_f_lock[SR];
							double delta_f_beat_expected = delta_f_rep * N3;
							
							logmsg("delta fbeat: measured=%e expected=%e difference=%e",
								delta_f_beat, delta_f_beat_expected, delta_f_beat - delta_f_beat_expected);
							
							double measured = delta_f_beat / delta_f_rep;
							SetCtrlVal(CalcNPanel, CALCN_N, measured);
							
							logmsg("measured N3=%.3f", measured);
							
							// back to nominal frep
							ad9912_ramp_frequency_w(&ad9912, 0, f0_DDS[0], FREP_STEP_SIZE);
							ad9912_set_frequency_w(&ad9912, 1, f0_DDS[1]);
							ad9912_set_frequency_w(&ad9912, 3, f0_DDS[3]);
							SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
							SetCtrlVal(MainPanel, PANEL_DDS2, ad9912.frequency[1]);
							SetCtrlVal(MainPanel, PANEL_DDS4, ad9912.frequency[3]);
							
							// done
							n_measurement[SR] = N_MEASUREMENT_NONE;
						}
						break;
				}
				
				// beatnote sign determination				
				if ((beatsign.measure != NONE) && 
				    (utc > beatsign.t0 + 2.0)) {

					int f_beat_sign, f_rep_sign = 0; 
					
					switch (beatsign.measure) {
					case LO:
						f_rep_sign = (Math1 > beatsign.f_rep_zero) ? -1 : 1;
						Sign1 = f_rep_sign;
						SetCtrlVal(MainPanel, PANEL_SIGN1, Sign1);
						break;
					case HG:
						f_rep_sign = (Math1 > beatsign.f_rep_zero) ? -1 : 1;
						f_beat_sign = (Ch2 > beatsign.f_beat_zero) ? -1 : 1;
						Sign2 = f_rep_sign * f_beat_sign;
						SetCtrlVal(MainPanel, PANEL_SIGN2, Sign2);
						break;
					case SR:
						f_rep_sign = (Math1 > beatsign.f_rep_zero) ? -1 : 1;
						f_beat_sign = (Ch3 > beatsign.f_beat_zero) ? -1 : 1;
						Sign3 = f_rep_sign * f_beat_sign;
						SetCtrlVal(MainPanel, PANEL_SIGN3, Sign3);
						break;
					}

					// back to original repetition rate
					ad9912_set_frequency_w(&ad9912, 0, beatsign.f0_DDS);
					SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);

					// measurement done
					beatsign.measure = NONE;
						
					// in the case of the optical beatnotes sign measurement
					// we induce fairly small steps in f_rep therefore it is
					// good to check that we get the sign of the comb locking 
					// beatnote right in those cases
					if (f_rep_sign != Sign1)
						logmessage(ERROR, "merasured f_rep_sign does not agree with previous determination!");
				}
				
				// select dedrift reference
				double f = 0.0;
				switch (dedrift.reference) {
					case MICROWAVE:
						f = Math2;
						break;
					case HG:
						f = Ch2 * 1062.5 / 1542.2;
						break;
				}

				// dedrift
				dedrift_update(f);
								
				// recenter
				recenter_update();

				struct tm *time = gmtime(&ev.time.tv_sec);
				// round to milliseconds
				int msec = round(ev.time.tv_usec / 1000.0);
				while (msec >= 1000) {
					time->tm_sec += 1;
					msec -= 1000;
				}
				// format time
				char timestr[24];
				int len = strftime(timestr, sizeof(timestr), "%d/%m/%Y %H:%M:%S", time);
				snprintf(timestr + len, sizeof(timestr) - len, ".%03d", msec);
				// display local time
				SetCtrlVal(MainPanel, PANEL_TIME, timestr);
				
				// run id derived from current date in the form YYMMDD
				char id[7];
				strftime(id, sizeof(id), "%y%m%d", time);
				
				// write datafiles
				for (struct datafile *d = datafiles; d->data; d++)
					datafile_append(d, id, timestr);
				
				// send Sr frequency (Math4) to Sr data logger
				sr_datalogger_send(&datalogger, utc, Math4);
				
				// publish data through ZMQ
				int r = zmq_xpub(zmqsocket, "RAW", &ev, sizeof(ev));
				if (r)
					logmessage(ERROR, "cannot send data through ZMQ socket: %s", zmq_strerror(r));
			}		
			break;
	}
}

int CVICALLBACK CB_OnFreqPlot (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			for (struct plot *plot = plots; plot->data; plot++) {
				if (plot->control == control)
					plot_toggle(plot);
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnAllanPlot (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			for (struct adev *adev = adevs; adev->data; adev++) {
				if (adev->control == control)
					adev_toggle(adev);
			}
			break;
	}
	return 0;
}

int  CVICALLBACK CB_ChangeDDSOut (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	switch (event) {
		case EVENT_COMMIT:
			double frequency;
			GetCtrlVal(MainPanel, control, &frequency);
			switch (control) {
				case PANEL_DDS1:
					ad9912_set_frequency_w(&ad9912, 0, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
					break;
				case PANEL_DDS2:
					ad9912_set_frequency_w(&ad9912, 1, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS2, ad9912.frequency[1]);
					break;
				case PANEL_DDS3:
					ad9912_set_frequency_w(&ad9912, 2, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS3, ad9912.frequency[2]);
					break;
				case PANEL_DDS4:
					ad9912_set_frequency_w(&ad9912, 3, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS4, ad9912.frequency[3]);
					break;
			}
			break;
	}
	return 0;
}

int  CVICALLBACK CB_ChangeDDSStep (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			double step;
			GetCtrlVal(panel, control, &step);
			switch (control)
			{
				case PANEL_DDS1STEP:
					SetCtrlAttribute(panel, PANEL_DDS1, ATTR_INCR_VALUE, step);
					break;
				case PANEL_DDS2STEP:
					SetCtrlAttribute(panel, PANEL_DDS2, ATTR_INCR_VALUE, step);
					break;
				case PANEL_DDS3STEP:
					SetCtrlAttribute(panel, PANEL_DDS3, ATTR_INCR_VALUE, step);
					break;
				case PANEL_DDS4STEP:
					SetCtrlAttribute(panel, PANEL_DDS4, ATTR_INCR_VALUE, step);
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_ChangeMath (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int len;
	char *string;
		
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlAttribute(panel, control, ATTR_STRING_TEXT_LENGTH, &len);
			string = (char *)malloc(sizeof(char) * (len + 1));
			GetCtrlVal(panel, control, string);
			switch (control) {
				case PANEL_MATHSTRING1:
					mupSetExpr(MathParser1, string);
					break;
				case PANEL_MATHSTRING2:
					mupSetExpr(MathParser2, string);
					break;
				case PANEL_MATHSTRING3:
					mupSetExpr(MathParser3, string);
					break;
				case PANEL_MATHSTRING4:
					mupSetExpr(MathParser4, string);
					break;
				case PANEL_MATHSTRING5:
					mupSetExpr(MathParser5, string);
					break;
			}
			free(string);
			break;
	}
	return 0;
}

int CVICALLBACK CB_ChangeN (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			switch (control)
			{
				case PANEL_N1:
					GetCtrlVal(panel, control, &N1);
					break;
				case PANEL_N2:
					GetCtrlVal(panel, control, &N2);
					break;
				case PANEL_N3:
					GetCtrlVal(panel, control, &N3);
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnAcceptN (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int measure;
	double measured;

	switch (event) {
		case EVENT_COMMIT:
			GetPanelAttribute(panel, ATTR_CALLBACK_DATA, &measure);
			GetCtrlVal(panel, CALCN_N, &measured);
			switch (measure) {
				case LO:
					N1 = round(measured);
					SetCtrlVal(MainPanel, PANEL_N1, N1);
					break;
				case HG:
					N2 = round(measured);
					SetCtrlVal(MainPanel, PANEL_N2, N2);
					break;
				case SR:
					N3 = round(measured);
					SetCtrlVal(MainPanel, PANEL_N3, N3);
					break;
			} 
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnNCalculus (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int visible;
	
	switch (event) {
		case EVENT_COMMIT:
			switch (control) {
				case PANEL_N1CALCULUS:
					GetPanelAttribute(CalcNPanel, ATTR_VISIBLE, &visible);
					if (! visible) {
						SetPanelAttribute(CalcNPanel, ATTR_CALLBACK_DATA, INT_TO_PTR(LO));
						SetPanelAttribute(CalcNPanel, ATTR_TITLE, "Measure N_Lo");
						SetCtrlVal(CalcNPanel, CALCN_INTEGRATIONTIME, integration_time[LO]);
						SetCtrlVal(CalcNPanel, CALCN_SLOPETIME, slope_time[LO]);
						SetCtrlVal(CalcNPanel, CALCN_DELTAFREQ, delta_f_lock[LO] / 1000.0);
						SetCtrlVal(CalcNPanel, CALCN_N, 0.0);
						DisplayPanel(CalcNPanel);
					}
					break;
				case PANEL_N2CALCULUS:
					GetPanelAttribute(CalcNPanel, ATTR_VISIBLE, &visible);
					if (! visible) {
						SetPanelAttribute(CalcNPanel, ATTR_CALLBACK_DATA, INT_TO_PTR(HG));
						SetPanelAttribute(CalcNPanel, ATTR_TITLE, "Measure N_Hg");
						SetCtrlVal(CalcNPanel, CALCN_INTEGRATIONTIME, integration_time[HG]);
						SetCtrlVal(CalcNPanel, CALCN_SLOPETIME, slope_time[HG]);
						SetCtrlVal(CalcNPanel, CALCN_DELTAFREQ, delta_f_lock[HG] / 1000.0);
						SetCtrlVal(CalcNPanel, CALCN_N, 0.0);
						DisplayPanel(CalcNPanel);
					} 
					break;
				case PANEL_N3CALCULUS:
					GetPanelAttribute(CalcNPanel, ATTR_VISIBLE, &visible);
					if (! visible) {
						SetPanelAttribute(CalcNPanel, ATTR_CALLBACK_DATA, INT_TO_PTR(SR));
						SetPanelAttribute(CalcNPanel, ATTR_TITLE, "Measure N_Sr");
						SetCtrlVal(CalcNPanel, CALCN_INTEGRATIONTIME, integration_time[SR]);
						SetCtrlVal(CalcNPanel, CALCN_SLOPETIME, slope_time[SR]);
						SetCtrlVal(CalcNPanel, CALCN_DELTAFREQ, delta_f_lock[SR] / 1000.0);
						SetCtrlVal(CalcNPanel, CALCN_N, 0.0);
						DisplayPanel(CalcNPanel);
					}
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnStartNCalculus (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	void *v;
	int measuring;
	
	switch (event) {
		case EVENT_COMMIT:
			GetPanelAttribute(panel, ATTR_CALLBACK_DATA, &v);
			measuring = PTR_TO_INT(v);
			switch (measuring) {
				case LO:
					GetCtrlVal(CalcNPanel, CALCN_INTEGRATIONTIME, &integration_time[LO]);
					GetCtrlVal(CalcNPanel, CALCN_SLOPETIME, &slope_time[LO]);
					GetCtrlVal(CalcNPanel, CALCN_DELTAFREQ, &delta_f_lock[LO]);
					// convert from kHz to Hz
					delta_f_lock[LO] = delta_f_lock[LO] * 1000.0;
					n_measurement[LO] = TRUE;
					break;
				case HG:
					GetCtrlVal(CalcNPanel, CALCN_INTEGRATIONTIME, &integration_time[HG]);
					GetCtrlVal(CalcNPanel, CALCN_SLOPETIME, &slope_time[HG]);
					GetCtrlVal(CalcNPanel, CALCN_DELTAFREQ, &delta_f_lock[HG]);
					// convert from kHz to Hz
					delta_f_lock[HG] = delta_f_lock[HG] * 1000.0;
					n_measurement[HG] = TRUE;
					break;
				case SR:
					GetCtrlVal(CalcNPanel, CALCN_INTEGRATIONTIME, &integration_time[SR]);
					GetCtrlVal(CalcNPanel, CALCN_SLOPETIME, &slope_time[SR]);
					GetCtrlVal(CalcNPanel, CALCN_DELTAFREQ, &delta_f_lock[SR]);
					// convert from kHz to Hz
					delta_f_lock[SR] = delta_f_lock[SR] * 1000.0;
					n_measurement[SR] = TRUE;
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnNStop (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	void *v;
	int measuring;
	
	switch (event) {
		case EVENT_COMMIT:
			HidePanel(CalcNPanel);
			GetPanelAttribute(panel, ATTR_CALLBACK_DATA, &v);
			measuring = PTR_TO_INT(v);
			switch (measuring) {
				case LO:
					n_measurement[LO] = FALSE;
					ad9912_ramp_frequency_w(&ad9912, 0, f0_DDS[0], FREP_STEP_SIZE);
					ad9912_set_frequency_w(&ad9912, 1, f0_DDS[1]);
					break;
				case HG:
					n_measurement[HG] = FALSE;
					ad9912_ramp_frequency_w(&ad9912, 0, f0_DDS[0], FREP_STEP_SIZE);
					ad9912_set_frequency_w(&ad9912, 1, f0_DDS[1]);
					ad9912_set_frequency_w(&ad9912, 2, f0_DDS[2]);
					break;
				case SR:
					n_measurement[SR] = FALSE;
					ad9912_ramp_frequency_w(&ad9912, 0, f0_DDS[0], FREP_STEP_SIZE);
					ad9912_set_frequency_w(&ad9912, 1, f0_DDS[1]);
					ad9912_set_frequency_w(&ad9912, 3, f0_DDS[3]);
					break;
			}
			
			// update DDS frequencies display
			SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
			SetCtrlVal(MainPanel, PANEL_DDS2, ad9912.frequency[1]);
			SetCtrlVal(MainPanel, PANEL_DDS3, ad9912.frequency[2]);
			SetCtrlVal(MainPanel, PANEL_DDS4, ad9912.frequency[3]);
			
			break;
	}
	return 0;
}

int  CVICALLBACK CB_OnFindSign (int panel, int control, int event, 
		void *callbackData, int eventData1, int eventData2)
{
	double step = 0.0;
	switch (event)
	{
		case EVENT_COMMIT:
			switch (control) {
 				case PANEL_FINDSIGN1:
					beatsign.measure = LO;
					beatsign.f_beat_zero = 0.0;
					step = f_lock_step[LO];
					break;
				case PANEL_FINDSIGN2:
					beatsign.measure = HG;
					beatsign.f_beat_zero = Ch2;
					step = f_lock_step[HG];
					break;
				case PANEL_FINDSIGN3:
					beatsign.measure = SR;
					beatsign.f_beat_zero = Ch3;
					step = f_lock_step[SR];
					break;
			}

			beatsign.t0 = utc;
			beatsign.f_rep_zero = Math1;

			// step the repetition rate
			beatsign.f0_DDS = ad9912.frequency[0];
			ad9912_set_frequency_w(&ad9912, 0, beatsign.f0_DDS + step);
			SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);

			break;
	}
	return 0;
}

int  CVICALLBACK CB_AdjustDDSFreq (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	double frequency;
	switch (event)
	{
		case EVENT_COMMIT:
			switch (control)
			{
				case PANEL_ADJUST_DDS2:
					frequency = ad9912.frequency[1] + 275000 - Ch4;
					ad9912_set_frequency_w(&ad9912, 1, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS2, ad9912.frequency[1]);
					break;
				case PANEL_ADJUST_DDS3:
					frequency = ad9912.frequency[2] + 10000 - Ch2;
					ad9912_set_frequency_w(&ad9912, 2, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS3, ad9912.frequency[2]);
					break;
				case PANEL_ADJUST_DDS4:
					frequency = ad9912.frequency[3] + 10000 - Ch3;
					ad9912_set_frequency_w(&ad9912, 3, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS4, ad9912.frequency[3]);
					break;
			}
			break;
	}
	return 0;
}

int  CVICALLBACK CB_OnChangeNdiv (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(MainPanel, PANEL_CHANGENDIV, &Ndiv);
			f0_DDS[0] = 880000000.0 / Ndiv;
			ad9912_set_frequency_w(&ad9912, 0, f0_DDS[0]);
			SetCtrlVal(MainPanel, PANEL_DDS1, ad9912.frequency[0]);
			break;
	}
	return 0;
}

int  CVICALLBACK CB_MeasureSlope (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	int enable;
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &enable);
			enable ? dedrift_update_enable() : dedrift_update_disable();
			break;			
	}
	return 0;
}

int  CVICALLBACK CB_OnResetSlope (int panel, int control, int event, 
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			dedrift.applied = 0.0; 
			SetCtrlVal(panel, PANEL_SLOPE_APPLIED, dedrift.applied);
			ad9956_set_w(&ad9956, dedrift.fDDS, dedrift.applied);
			logmsg("dedrift: reset");
			break;
	}
	return 0;
}
  
int  CVICALLBACK CB_ChangeSlopeTime (int panel, int control, int event, 
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(MainPanel, PANEL_SLOPETIME, &dedrift.interval); 
			break;
	}
	return 0;
}

int  CVICALLBACK CB_OnDedriftSettingsChange (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			switch (control)
			{
				case PANEL_DEDRIFT_PROPORTIONAL:
					// include correction proportional to frequency
				   	GetCtrlVal(panel, control, &dedrift.proportional);
					break;
				case PANEL_DEDRIFT_DOUBLE_CORR:
					// double slope correction
					GetCtrlVal(panel, control, &dedrift.x2);
					break;
				case PANEL_DEDRIFT_KEEP_FREQ:
					// keep current dedrifting frequency when dedrifting is disabled
				   	GetCtrlVal(panel, control, &dedrift.keep_freq);
					break;
				case PANEL_DEDRIFT_KEEP_SLOPE:
					// keep current dedrifting slope when dedrifting is disabled
				   	GetCtrlVal(panel, control, &dedrift.keep_slope);
					break;
			}
 			break;
	}
	return 0;
}

int  CVICALLBACK CB_RecenterEnable (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &recenter.active);
			recenter.t0 = utc;
			rollmean_zero(&rollmean_ch2);
			rollmean_zero(&rollmean_ch3);
			rollmean_zero(&rollmean_ch4);
 			break;
	}
	return 0;
}

int  CVICALLBACK CB_OnStopSlopeCancellingOnUnlocked (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &dedrift.safety);
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnSlopeReference (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &dedrift.reference);
			break;
	}
	return 0;
}

int CVICALLBACK CB_SetSlope (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &dedrift.applied);
			ad9956_set_sweep_rate_w(&ad9956, dedrift.applied);
			break;
	}
	return 0;
}

int CVICALLBACK CB_InvertSlopeSign (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int invert;
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &invert);
			dedrift.sign = invert ? -1 : +1;
			break;
	}
	return 0;
}

int CVICALLBACK CB_ResetDedriftDDS (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			// stop slope measurement and reset slope 
			dedrift.enabled = FALSE;
			SetCtrlVal(panel, PANEL_MEASURE_SLOPE, 0);
			dedrift.applied = 0.0;
			SetCtrlVal(panel, PANEL_SLOPE_APPLIED, dedrift.applied);
			// reset DDS
			ad9956_set_w(&ad9956, dedrift.fDDS, dedrift.applied);
			break;
	}
	return 0;
}

int CVICALLBACK CB_ShowLog (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int visible;
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &visible);
			logger_panel_visible(visible);
			break;
	}
	return 0;
}

int CVICALLBACK CB_ShowError (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			logger_panel_visible(TRUE);
			SetCtrlVal(panel, control, FALSE);
			SetCtrlVal(panel, PANEL_SHOWLOG, TRUE);
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnLoggingPanelEvent(int panel, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_CLOSE:
			logger_panel_visible(0);
			SetCtrlVal(MainPanel, PANEL_SHOWLOG, 0);
			break;
	}
	return 0;
}

int CVICALLBACK CB_BeatnoteSign (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			switch(control) {
				case PANEL_SIGN1:
					GetCtrlVal(panel, control, &Sign1);
					break;
				case PANEL_SIGN2:
					GetCtrlVal(panel, control, &Sign2);
					break;
				case PANEL_SIGN3:
					GetCtrlVal(panel, control, &Sign3);
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_SaveData (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			for (struct datafile *d = datafiles; d->data; d++) {
				if (d->control == control)
					GetCtrlVal(panel, control, &(d->write));
			}
			break;
	}
	return 0;
}


int CVICALLBACK CB_RecenterInterval (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &recenter.interval);
			break;
	}
	return 0;
}

int CVICALLBACK CB_RecenterChannel (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			switch (control)
			{
				case PANEL_RECENTER_LO:
					GetCtrlVal(panel, control, &recenter.enabled[LO]);
					break;
				case PANEL_RECENTER_HG:
					GetCtrlVal(panel, control, &recenter.enabled[HG]);
					break;
				case PANEL_RECENTER_SR:
					GetCtrlVal(panel, control, &recenter.enabled[SR]);
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_RecenterThreshold (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			switch (control)
			{
				case PANEL_RECENTER_THRESHOLD_LO:
					GetCtrlVal(panel, control, &recenter.threshold[LO]);
					break;
				case PANEL_RECENTER_THRESHOLD_HG:
					GetCtrlVal(panel, control, &recenter.threshold[HG]);
					break;
				case PANEL_RECENTER_THRESHOLD_SR:
					GetCtrlVal(panel, control, &recenter.threshold[SR]);
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_SrDatalogger (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &datalogger.enabled);
			break;
	}
	return 0;
}

int CVICALLBACK CB_DedriftDDSFreq (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &dedrift.fDDS);
			ad9956_set_w(&ad9956, dedrift.fDDS, dedrift.applied);
			break;
	}
	return 0;
}


//
// N estimate 
//

static void estimateN (void)
{
	double nu, fbeat, frep, N;
	int sign;
	
	GetCtrlVal(EstimateNPanel, ESTIMATEN_FREQUENCY, &nu);
	GetCtrlVal(EstimateNPanel, ESTIMATEN_FREP, &frep);
	GetCtrlVal(EstimateNPanel, ESTIMATEN_FBEAT, &fbeat);
	GetCtrlVal(EstimateNPanel, ESTIMATEN_SIGN, &sign);
		
	N = (nu * 1.0e12 - sign * fbeat) / frep;
	
	SetCtrlVal(EstimateNPanel, ESTIMATEN_N, N);
}

int CVICALLBACK cb_onEstimateN (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int visible;
	
	switch (event)
	{
		case EVENT_COMMIT:
			// reset N estimate
			SetCtrlVal(EstimateNPanel, ESTIMATEN_N, 0.0);
			// set current frep
			SetCtrlVal(EstimateNPanel, ESTIMATEN_FREP, 250e6 + Math1);
			switch (control) {
				case PANEL_ESTIMATE_N2:
					// expected frequency
					SetCtrlVal(EstimateNPanel, ESTIMATEN_FREQUENCY, HG_FREQUENCY);
					SetCtrlVal(EstimateNPanel, ESTIMATEN_WAVELENGTH, HG_WAVELENGTH);
					// sign
					SetCtrlVal(EstimateNPanel, ESTIMATEN_SIGN, (int)Sign2);
					// f_DDS
					SetCtrlVal(EstimateNPanel, ESTIMATEN_FDDS, ad9912.frequency[2]);
					// f_counter
					SetCtrlVal(EstimateNPanel, ESTIMATEN_FDDS, Ch2);
					// f_beat
					SetCtrlVal(EstimateNPanel, ESTIMATEN_FBEAT, ad9912.frequency[2] - Ch2);
					
					SetPanelAttribute(EstimateNPanel, ATTR_TITLE, "Estimate N_Hg");
					SetPanelAttribute(EstimateNPanel, ATTR_CALLBACK_DATA, INT_TO_PTR(HG));
					break;
				case PANEL_ESTIMATE_N3:
					// expected frequency
					SetCtrlVal(EstimateNPanel, ESTIMATEN_FREQUENCY, SR_FREQUENCY);
					SetCtrlVal(EstimateNPanel, ESTIMATEN_WAVELENGTH, SR_WAVELENGTH);
					// sign
					SetCtrlVal(EstimateNPanel, ESTIMATEN_SIGN, (int)Sign3);
					// f_DDS
					SetCtrlVal(EstimateNPanel, ESTIMATEN_FDDS, ad9912.frequency[3]);
					// f_counter
					SetCtrlVal(EstimateNPanel, ESTIMATEN_FDDS, Ch3);
					// f_beat
					SetCtrlVal(EstimateNPanel, ESTIMATEN_FBEAT, ad9912.frequency[3] - Ch3);

					SetPanelAttribute(EstimateNPanel, ATTR_TITLE, "Estimate N_Sr");
					SetPanelAttribute(EstimateNPanel, ATTR_CALLBACK_DATA, INT_TO_PTR(SR));
					break;
			}
			
			// display dialog
			GetPanelAttribute(EstimateNPanel, ATTR_VISIBLE , &visible);
			if (! visible)
				DisplayPanel(EstimateNPanel);
			
			// compute
			estimateN();
			break;
	}
	return 0;
}

int CVICALLBACK cb_onEstimateNWaveleght (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	double wavelenght, frequency;
	
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &wavelenght);
			frequency = SPEED_OF_LIGHT / (wavelenght * 1.0e-9) / 1.0e12;
			SetCtrlVal(panel, ESTIMATEN_FREQUENCY, frequency);
			estimateN();
			break;
	}
	return 0;
}

int CVICALLBACK cb_onEstimateNFrequency (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	double wavelenght, frequency;
	
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &frequency);
			wavelenght = SPEED_OF_LIGHT / (frequency * 1.0e12) / 1.0e-9;
			SetCtrlVal(panel, ESTIMATEN_WAVELENGTH, wavelenght);
			estimateN();
			break;
	}
	return 0;
}

int CVICALLBACK cb_onEstimateNChange (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			estimateN();
			break;
	}
	return 0;
}

int CVICALLBACK cb_onEstimateNClose (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int visible;
	
	switch (event)
	{
		case EVENT_COMMIT:
			GetPanelAttribute(panel, ATTR_VISIBLE, &visible);
			if (visible)
				HidePanel(panel);
			break;
	}
	return 0;
}

int CVICALLBACK cb_onEstimateNSet (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	void *v;
	double n;
	int estimate = 0;
	
	switch (event)
	{
		case EVENT_COMMIT:
			GetPanelAttribute(panel, ATTR_CALLBACK_DATA, &v);
			estimate = PTR_TO_INT(v);
			switch (estimate) {
				case HG:
					GetCtrlVal(panel, ESTIMATEN_N, &n);
					N2 = round(n);
					SetCtrlVal(MainPanel, PANEL_N2, N2);
					break;
				case SR:
					GetCtrlVal(panel, ESTIMATEN_N, &n);
					N3 = round(n);
					SetCtrlVal(MainPanel, PANEL_N3, N3);
					break;
			}
			HidePanel(panel);
			break;
	}
	return 0;
}