Changeset 17027


Ignore:
Timestamp:
08/19/13 16:36:59 (11 years ago)
Author:
tbretz
Message:
Went over the dim services and updated them with new information or removed obsolete information; got the old and the new algorithm to work; improved steering (automatic ramping when a calibration is started); shortened the command and removed obsolete commands; removed a lot of old obsolete code (just temperature control and the orginal amplitude feedback); added new three point calibration.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/FACT++/src/feedback.cc

    r16967 r17027  
    11#include <valarray>
     2#include <algorithm>
    23
    34#include "Dim.h"
     
    89#include "Configuration.h"
    910#include "Console.h"
    10 #include "Converter.h"
    1111#include "externals/PixelMap.h"
    1212
     
    1515#include "LocalControl.h"
    1616
    17 #include "HeadersFAD.h"
    1817#include "HeadersFSC.h"
    1918#include "HeadersBIAS.h"
     
    3029{
    3130private:
    32     enum control_t
    33     {
    34         kIdle,
    35         kTemp,
    36         kFeedback,
    37         kFeedbackGlobal,
    38         kCurrents,
    39         kCurrentsNew,
    40     };
    41 
    42     control_t fControlType;
    43 
    4431    PixelMap fMap;
    4532
     33    bool fIsVerbose;
     34    bool fEnableOldAlgorithm;
     35
    4636    DimVersion fDim;
    47     DimDescribedState fDimFAD;
     37
    4838    DimDescribedState fDimFSC;
    4939    DimDescribedState fDimBias;
    5040
    51     DimDescribedService fDimReference;
    52     DimDescribedService fDimDeviation;
    5341    DimDescribedService fDimCalibration;
     42    DimDescribedService fDimCalibration2;
     43    DimDescribedService fDimCalibrationR8;
    5444    DimDescribedService fDimCurrents;
     45
     46    vector<float>    fCalibCurrentMes[6]; // Measured calibration current at six different levels
     47    vector<float>    fCalibVoltage[6];    // Corresponding voltage as reported by biasctrl
    5548
    5649    vector<int64_t>  fCurrentsAvg;
    5750    vector<int64_t>  fCurrentsRms;
    5851
     52    vector<float>    fVoltGapd;     // Nominal breakdown voltage + 1.1V
     53    vector<float>    fBiasVolt;     // Output voltage as reported by bias crate (voltage between R10 and R8)
     54    vector<uint16_t> fBiasDac;      // Dac value corresponding to the voltage setting
     55
    5956    vector<float>    fCalibration;
    60     vector<float>    fVoltGapd;
    61     vector<float>    fBiasVolt;
    62 
    63     vector<vector<float>> fData;
     57    vector<float>    fCalibDeltaI;
     58    vector<float>    fCalibR8;
    6459
    6560     int64_t fCursorCur;
    66     uint64_t fCursorAmpl;
    67     uint64_t fCursorTemp;
    68 
    69     Time fBiasLast;
    70     Time fStartTime;
    71     Time fCalibTime;
    72 
    73     valarray<double> fPV[3];  // Process variable (intgerated/averaged amplitudes)
    74     valarray<double> fSP;     // Set point        (target amplitudes)
    75 
    76     double fKp;    // Proportional constant
    77     double fKi;    // Integral     constant
    78     double fKd;    // Derivative   constant
    79     double fT;     // Time         constant (cycle time)
    80     double fGain;  // Gain (conversion from a DRS voltage deviation into a BIAS voltage change at G-APD reference voltage)
    81 
    82     double fT21;
    83 
    84     double fBiasOffset;
     61
     62    Time fTimeCalib;
     63    Time fTimeTemp;
     64
     65    double fUserOffset;
    8566    double fTempOffset;
    86     double fCalibrationOffset;
    87     double fAppliedOffset;
     67    double fTemp;
    8868
    8969    uint16_t fCurrentRequestInterval;
    9070    uint16_t fNumCalibIgnore;
    9171    uint16_t fNumCalibRequests;
    92 
    93     bool fOutputEnabled;
     72    uint16_t fCalibStep;
     73
     74    // ============================= Handle Services ========================
     75
     76    int HandleBiasStateChange()
     77    {
     78        if (fDimBias.state()==BIAS::State::kVoltageOn && GetCurrentState()==Feedback::State::kCalibrating)
     79        {
     80            Dim::SendCommandNB("BIAS_CONTROL/REQUEST_STATUS");
     81            Info("Starting calibration step "+to_string(fCalibStep));
     82        }
     83
     84        if (fDimBias.state()==BIAS::State::kVoltageOff && GetCurrentState()==Feedback::State::kInProgress)
     85            return Feedback::State::kCalibrated;
     86
     87        return GetCurrentState();
     88    }
     89    // ============================= Handle Services ========================
     90
     91    bool CheckEventSize(size_t has, const char *name, size_t size)
     92    {
     93        if (has==size)
     94            return true;
     95
     96        // Disconnected
     97        if (has==0)
     98            return false;
     99
     100        ostringstream msg;
     101        msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
     102        Fatal(msg);
     103        return false;
     104    }
     105
     106    int HandleBiasNom(const EventImp &evt)
     107    {
     108        if (evt.GetSize()>=416*sizeof(float))
     109        {
     110            fVoltGapd.assign(evt.Ptr<float>(), evt.Ptr<float>()+416);
     111            Info("Nominal bias voltages and calibration resistor received.");
     112        }
     113
     114        return GetCurrentState();
     115    }
     116
     117    int HandleBiasVoltage(const EventImp &evt)
     118    {
     119        if (evt.GetSize()>=416*sizeof(float))
     120            fBiasVolt.assign(evt.Ptr<float>(), evt.Ptr<float>()+416);
     121        return GetCurrentState();
     122    }
     123
     124    int HandleBiasDac(const EventImp &evt)
     125    {
     126        if (evt.GetSize()>=416*sizeof(uint16_t))
     127            fBiasDac.assign(evt.Ptr<uint16_t>(), evt.Ptr<uint16_t>()+416);
     128        return GetCurrentState();
     129    }
    94130
    95131    int HandleCameraTemp(const EventImp &evt)
    96132    {
    97         if (fControlType!=kTemp && fControlType!=kCurrents && fControlType!=kCurrentsNew)
    98             return GetCurrentState();
    99 
    100         if (evt.GetSize()!=60*sizeof(float))
    101             return GetCurrentState();
     133        if (!CheckEventSize(evt.GetSize(), "HandleCameraTemp", 60*sizeof(float)))
     134        {
     135            fTimeTemp = Time(Time::none);
     136            return GetCurrentState();
     137        }
    102138
    103139        const float *ptr = evt.Ptr<float>();
     
    120156        avgt /= numt; // [deg C]
    121157
    122         fTempOffset = (avgt-25)*4./70; // [V]
    123 
    124         fCursorTemp++;
    125 
    126         return fControlType==kCurrentsNew ? HandleCurrentControlNew() : HandleCurrentControl();
    127     }
    128 
    129     int HandleCurrentControlNew()
    130     {
    131         if (GetCurrentState()==Feedback::State::kCalibrating && fBiasOffset>fTempOffset-1.2)
    132         {
    133             fCursorTemp = 0;
    134 
    135             ostringstream msg;
    136             msg << " (applied calibration offset " << fBiasOffset << "V exceeds temperature correction " << fTempOffset << "V - 1.2V.";
    137             Warn("Trying to calibrate above G-APD breakdown volatge!");
    138             Warn(msg);
    139             return GetCurrentState();
    140         }
     158        fTimeTemp   = evt.GetTime();
     159        fTempOffset = (avgt-25)*0.0561765; // [V] From Hamamatsu datasheet
     160        fTemp       = avgt;
     161
     162        return GetCurrentState();
     163    }
     164
     165    pair<vector<float>, vector<float>> AverageCurrents(const int16_t *ptr, int n)
     166    {
     167        if (fCursorCur++>=0)
     168        {
     169            for (int i=0; i<BIAS::kNumChannels; i++)
     170            {
     171                fCurrentsAvg[i] += ptr[i];
     172                fCurrentsRms[i] += ptr[i]*ptr[i];
     173            }
     174        }
     175
     176        if (fCursorCur<n)
     177            return make_pair(vector<float>(), vector<float>());
     178
     179        const double conv = 5e-3/4096;
     180
     181        vector<float> rms(BIAS::kNumChannels);
     182        vector<float> avg(BIAS::kNumChannels);
     183        for (int i=0; i<BIAS::kNumChannels; i++)
     184        {
     185            avg[i] = double(fCurrentsAvg[i])/fCursorCur * conv;
     186            rms[i] = double(fCurrentsRms[i])/fCursorCur * conv * conv;
     187
     188            rms[i] = sqrt(rms[i]-avg[i]*avg[i]);
     189        }
     190
     191        return make_pair(avg, rms);
     192    }
     193
     194    int HandleCalibration(const EventImp &evt)
     195    {
     196        const uint16_t dac = 256+512*fCalibStep; // Command value
     197
     198        // Only the channels which are no spare channels are ramped
     199        if (std::count(fBiasDac.begin(), fBiasDac.end(), dac)!=320)
     200            return GetCurrentState();
     201
     202        const auto rc = AverageCurrents(evt.Ptr<int16_t>(), fNumCalibRequests);
     203        if (rc.first.size()==0)
     204        {
     205            Dim::SendCommandNB("BIAS_CONTROL/REQUEST_STATUS");
     206            return GetCurrentState();
     207        }
     208
     209        const vector<float> &avg = rc.first;
     210        const vector<float> &rms = rc.second;
     211
     212        // Current through resistor R8
     213        fCalibCurrentMes[fCalibStep] = avg;       // [A]
     214        fCalibVoltage[fCalibStep]    = fBiasVolt; // [V]
     215
     216        // ------------------------- Update calibration data --------------------
     217
     218        struct cal_data
     219        {
     220            uint32_t dac;
     221            float    U[416];
     222            float    Iavg[416];
     223            float    Irms[416];
     224
     225            cal_data() { memset(this, 0, sizeof(cal_data)); }
     226        } __attribute__((__packed__));
     227
     228        cal_data cal;
     229        cal.dac = dac;
     230        memcpy(cal.U,    fBiasVolt.data(), 416*sizeof(float));
     231        memcpy(cal.Iavg, avg.data(),       416*sizeof(float));
     232        memcpy(cal.Irms, rms.data(),       416*sizeof(float));
     233
     234        fDimCalibration2.setData(fCalibration);
     235        fDimCalibration2.Update(fTimeCalib);
     236
     237        // -------------------- Start next calibration steo ---------------------
     238
     239        if (++fCalibStep<6)
     240        {
     241            fCursorCur  = -fNumCalibIgnore;
     242            fCurrentsAvg.assign(BIAS::kNumChannels, 0);
     243            fCurrentsRms.assign(BIAS::kNumChannels, 0);
     244
     245            Dim::SendCommandNB("BIAS_CONTROL/SET_GLOBAL_DAC", uint32_t(256+512*fCalibStep));
     246
     247            return GetCurrentState();
     248        }
     249
     250        // --------------- Calculate old style calibration ----------------------
     251
     252        fCalibration.resize(BIAS::kNumChannels*4);
     253
     254        float *pavg  = fCalibration.data();
     255        float *prms  = fCalibration.data()+BIAS::kNumChannels;
     256        float *pres  = fCalibration.data()+BIAS::kNumChannels*2;
     257        float *pUmes = fCalibration.data()+BIAS::kNumChannels*3;
     258
     259        for (int i=0; i<BIAS::kNumChannels; i++)
     260        {
     261            const double I = fCalibCurrentMes[5][i]; // [A]
     262            const double U = fBiasVolt[i];           // [V]
     263
     264            pavg[i]  = I*1e6;                        // [uA]
     265            prms[i]  = rms[i]*1e6;                   // [uA]
     266            pres[i]  = U/I;                          // [Ohm]
     267            pUmes[i] = U;                            // [V]
     268        }
     269
     270        fDimCalibration.setData(fCalibration);
     271        fDimCalibration.Update(fTimeCalib);
     272
     273        // -------------------- New style calibration --------------------------
     274
     275        fCalibDeltaI.resize(BIAS::kNumChannels);
     276        fCalibR8.resize(BIAS::kNumChannels);
     277
     278        // Linear regression of the values at 256+512*N for N={ 3, 4, 5 }
     279        for (int i=0; i<BIAS::kNumChannels; i++)
     280        {
     281            // x: Idac
     282            // y: Iadc
     283
     284            double x  = 0;
     285            double y  = 0;
     286            double xx = 0;
     287            double xy = 0;
     288
     289            const int beg = 3;
     290            const int end = 5;
     291            const int len = end-beg+1;
     292
     293            for (int j=beg; j<=end; j++)
     294            {
     295                const double Idac = (256+512*j)*1e-3/4096;
     296
     297                x  += Idac;
     298                xx += Idac*Idac;
     299                y  += fCalibCurrentMes[j][i];
     300                xy += fCalibCurrentMes[j][i]*Idac;
     301            }
     302
     303            const double m1 = xy - x*y / len;
     304            const double m2 = xx - x*x / len;
     305
     306            const double m = m2==0 ? 0 : m1/m2;
     307
     308            const double t = (y - m*x) / len;
     309
     310            fCalibDeltaI[i] = t;     // [A]
     311            fCalibR8[i]     = 100/m; // [Ohm]
     312        }
     313
     314        vector<float> v(BIAS::kNumChannels*2);
     315        v.insert(v.end(), fCalibDeltaI.begin(), fCalibDeltaI.end());
     316        v.insert(v.end(), fCalibR8.begin(),     fCalibR8.end());
     317
     318        fDimCalibrationR8.setData(v);
     319        fDimCalibrationR8.Update(fTimeCalib);
     320
     321        // ---------------------------------------------------------------------
     322
     323        Info("Calibration successfully done.");
     324        Dim::SendCommandNB("BIAS_CONTROL/SET_ZERO_VOLTAGE");
     325
     326        return Feedback::State::kCalibrated;
     327    }
     328
     329    int HandleBiasCurrent(const EventImp &evt)
     330    {
     331        if (!CheckEventSize(evt.GetSize(), "HandleBiasCurrent", BIAS::kNumChannels*sizeof(uint16_t)))
     332            return Feedback::State::kConnected;
     333
     334        if (GetCurrentState()<Feedback::State::kCalibrating)
     335            return GetCurrentState();
     336
     337        // FIXME? Allow for calibrated currents also during ramping?
     338        if ((GetCurrentState()!=Feedback::State::kWaitingForData || fDimBias.state()!=BIAS::State::kVoltageOff) &&
     339            fDimBias.state()!=BIAS::State::kVoltageOn)
     340            return GetCurrentState();
     341
     342        // ------------------------------- HandleCalibration -----------------------------------
     343        if (GetCurrentState()==Feedback::State::kCalibrating)
     344            return HandleCalibration(evt);
     345
     346        // ---------------------- Calibrated, WaitingForData, InProgress -----------------------
     347
     348        // We are waiting but no valid temperature yet, go on waiting
     349        if (GetCurrentState()==Feedback::State::kWaitingForData &&
     350            (!fTimeTemp.IsValid() || Time()-fTimeTemp>boost::posix_time::minutes(5)))
     351            return GetCurrentState();
     352
     353        // We are already in progress but no valid temperature update anymore
     354        if (GetCurrentState()==Feedback::State::kInProgress &&
     355            (!fTimeTemp.IsValid() || Time()-fTimeTemp>boost::posix_time::minutes(5)))
     356        {
     357            Warn("Current control in progress, but last received temperature older than 5min... switching voltage off.");
     358            Dim::SendCommandNB("BIAS_CONTROL/SET_ZERO_VOLTAGE");
     359            return Feedback::State::kCalibrated;
     360        }
     361
     362        // ---------------------- Calibrated, WaitingForData, InProgress -----------------------
     363
     364        const vector<float> &Imes = AverageCurrents(evt.Ptr<int16_t>(), 3).first;
     365        if (Imes.size()==0)
     366            return GetCurrentState();
     367
     368        fCurrentsAvg.assign(416, 0);
     369        fCurrentsRms.assign(416, 0);
     370        fCursorCur = 0;
     371
     372        // -------------------------------------------------------------------------------------
     373
     374        // Nominal overvoltage (w.r.t. the bias setup values)
     375        const double overvoltage = GetCurrentState()<Feedback::State::kWaitingForData ? 0 : fUserOffset;
    141376
    142377        double avg[2] = {   0,   0 };
    143378        double min[2] = {  90,  90 };
    144379        double max[2] = { -90, -90 };
    145         int    num[2] = {   0,   0 };
    146 
    147         vector<double> med[2];
     380        int    num[3] = {   0,   0,   0 };
     381
     382        vector<double> med[3];
    148383        med[0].resize(416);
    149384        med[1].resize(416);
    150 
    151         //const float *Ravg = fCalibration.data()+BIAS::kNumChannels*2; // Measured resistance
    152 
    153         vector<float> vec(2*BIAS::kNumChannels+2);
    154 
    155         vec[BIAS::kNumChannels*2]   = fTempOffset;
    156         vec[BIAS::kNumChannels*2+1] = fBiasOffset;
    157 
    158         float *Uoff = vec.data()+BIAS::kNumChannels;
    159 
    160         if (GetCurrentState()==Feedback::State::kCalibrating)
    161             for (int i=0; i<BIAS::kNumChannels; i++)
    162                 Uoff[i] = fBiasOffset;
    163         else
    164             for (int i=0; i<BIAS::kNumChannels; i++)
    165                 Uoff[i] = fTempOffset+fBiasOffset;
    166 
    167         if (fControlType==kCurrentsNew)
    168         {
    169             // Would be a devision by zero. We need informations first.
    170             if (fCursorCur==0)
    171                 return GetCurrentState();
    172 
    173             vector<double> dI;
    174             vector<double> R8;
    175             vector<double> R9;
    176 
    177             for (int i=0; i<BIAS::kNumChannels; i++)
     385        med[2].resize(416);
     386
     387        struct dim_data
     388        {
     389            float I[416];
     390            float Iavg;
     391            float Irms;
     392            float Imed;
     393            float Idev;
     394            uint32_t N;
     395            float Tdiff;
     396            float Uov[416];
     397            float Unom;
     398            float dUtemp;
     399
     400            dim_data() { memset(this, 0, sizeof(dim_data)); }
     401        } __attribute__((__packed__));
     402
     403        dim_data data;
     404
     405        data.Unom   = overvoltage;
     406        data.dUtemp = fTempOffset;
     407
     408        vector<float> vec(416);
     409
     410        cout << setprecision(4) << endl;
     411
     412        if (fEnableOldAlgorithm)
     413        {
     414            // Pixel  583: 5 31 == 191 (5)  C2 B3 P3
     415            // Pixel  830: 2  2 ==  66 (4)  C0 B8 P1
     416            // Pixel 1401: 6  1 == 193 (5)  C2 B4 P0
     417
     418            // 3900 Ohm/n + 1000 Ohm + 1100 Ohm  (with n=4 or n=5)
     419            const double R[2] = { 3075, 2870 };
     420
     421            const float *Iavg = fCalibration.data();                      // Offset at U=fCalibrationOffset
     422            const float *Ravg = fCalibration.data()+BIAS::kNumChannels*2; // Measured resistance
     423
     424            for (int i=0; i<320; i++)
    178425            {
    179426                const PixelMapEntry &hv = fMap.hv(i);
     
    181428                    continue;
    182429
    183                 // Nominal breakdown voltage (includes overvoltage already)
    184                 double Ubd = fVoltGapd[i];
    185 
    186                 // Nominal breakdown voltage excluding overvoltage of 1.1V
    187                 Ubd -= 1.1;
    188 
    189                 // Correct breakdown voltage for temperature dependence
    190                 Ubd += fTempOffset;
     430                // Average measured current
     431                const double Im = Imes[i] * 1e6; // [uA]
     432
     433                // Group index (0 or 1) of the of the pixel (4 or 5 pixel patch)
     434                const int g = hv.group();
     435
     436                // Serial resistors in front of the G-APD
     437                double Rg = R[g];
     438
     439                // This is assuming that the broken pixels have a 390 Ohm instead of 3900 Ohm serial resistor
     440                if (i==66)                // Pixel 830(66)
     441                    Rg = 2400;            // 2400 = (3/3900 + 1/390) + 1000 + 1100
     442                if (i==191 || i==193)     // Pixel 583(191) / Pixel 1401(193)
     443                    Rg = 2379;            // 2379 = (4/3900 + 1/390) + 1000 + 1100
     444
     445                const double r = 1./(1./Rg + 1./Ravg[i]); // [Ohm]
     446
     447                // Offset induced by the voltage above the calibration point
     448                const double Ubd = fVoltGapd[i] + fTempOffset;
     449                const double U0  = Ubd + overvoltage - fCalibVoltage[5][i]; // appliedOffset-fCalibrationOffset;
     450                const double dI  = U0/Ravg[i]; // [V/Ohm]
     451
     452                // Offset at the calibration point (make sure that the calibration is
     453                // valid (Im[i]>Iavg[i]) and we operate above the calibration point)
     454                const double I = Im>Iavg[i] ? Im - Iavg[i] : 0; // [uA]
     455
     456                // Make sure that the averaged resistor is valid
     457                const double dU = Ravg[i]>10000 ? r*(I*1e-6 - dI) : 0;
     458
     459                if (i==2)
     460                    cout << dU << endl;;
     461
     462                vec[i] = Ubd + overvoltage + dU;
     463
     464                // Calculate statistics only for channels with a valid calibration
     465                if (Iavg[i]>0)
     466                {
     467                    med[g][num[g]] = dU;
     468                    avg[g] += dU;
     469                    num[g]++;
     470
     471                    if (dU<min[g])
     472                        min[g] = dU;
     473                    if (dU>max[g])
     474                        max[g] = dU;
     475
     476                    data.I[i]  = Imes[i]*1e6 - fBiasVolt[i]/Ravg[i]*1e6;
     477                    data.I[i] /= hv.count();
     478
     479                    if (i==66)
     480                        data.I[i] /= 1.3;
     481                    if (i==191 || i==193)
     482                        data.I[i] /= 1.4;
     483
     484                    data.Iavg += data.I[i];
     485                    data.Irms += data.I[i]*data.I[i];
     486
     487                    med[2][num[2]++] = data.I[i];
     488                }
     489            }
     490        }
     491        else
     492        {
     493            /* ================================= new ======================= */
     494
     495            for (int i=0; i<320/*BIAS::kNumChannels*/; i++)
     496            {
     497                const PixelMapEntry &hv = fMap.hv(i);
     498                if (!hv)
     499                    continue;
    191500
    192501                // Number of G-APDs in this patch
    193                 const int N = hv.group() ? 5 : 4;
     502                const int N = hv.count();
    194503
    195504                // Average measured ADC value for this channel
    196                 const double adc = double(fCurrentsAvg[i])/fCursorCur * (5000/4096.); // [uA]
    197 
    198                 // Current through ~100Ohm measurement resistor
    199                 const double I8 = (adc-dI[i])*100/R8[i];
     505                const double adc = Imes[i]/* * (5e-3/4096)*/; // [A]
     506
     507                // Current through ~100 Ohm measurement resistor
     508                const double I8 = (adc-fCalibDeltaI[i])*fCalibR8[i]/100;
     509
     510                // Applied voltage at calibration resistors, according to biasctrl
     511                const double U9 = fBiasVolt[i];
     512
     513                // Current through calibration resistors (R9)
     514                // This is uncalibrated, biut since the corresponding calibrated
     515                // value I8 is subtracted, the difference should yield a correct value
     516                const double I9 = fBiasDac[i] * (1e-3/4096);//U9/R9;   [A]
     517
     518                // Current in R4/R5 branch
     519                const double Iout = I8>I9 ? I8 - I9 : 0;
    200520
    201521                // Serial resistors (one 1kOhm at the output of the bias crate, one 1kOhm in the camera)
     
    211531                    R5 = 390/1.4;          // 379 = 1/(4/3900 + 1/390)
    212532
    213                 // Total resistance of branch with diode
     533                // Total resistance of branch with diodes
    214534                const double R = R4+R5;
    215535
    216                 // Applied voltage at calibration resistors, according to
    217                 // biasctrl
    218                 const double U9 = fBiasVolt[i];
    219 
    220                 // Current through calibration resistors
    221                 // FIXME: Get that from biasctrl!!!
    222                 const double I9 = U9/R9[i];
    223 
    224                 // Current in R4/R5 branch
    225                 const double Iout = I8>I9 ? I8 - I9 : 0;
    226 
    227                 // Voltage drop in R4/R5 branch
     536                // For the patches with a broken resistor - ignoring the G-APD resistance -
     537                // we get:
     538                //
     539                // I[R=3900] =  Iout *      1/(10+(N-1))   = Iout        /(N+9)
     540                // I[R= 390] =  Iout * (1 - 1/ (10+(N-1))) = Iout * (N+8)/(N+9)
     541                //
     542                // I[R=390] / I[R=3900] = N+8
     543                //
     544                // Udrp = Iout*3900/(N+9) + Iout*1000 + Iout*1000 = Iout * R
     545
     546                // Voltage drop in R4/R5 branch (for the G-APDs with correct resistor)
    228547                const double Udrp = R*Iout;
    229548
    230                 // Current overvoltage
     549                // Nominal breakdown voltage with correction for temperature dependence
     550                const double Ubd = fVoltGapd[i] + fTempOffset;
     551
     552                // Current overvoltage (at a G-APD with the correct 3900 Ohm resistor)
    231553                const double Uov = U9-Udrp-Ubd>0 ? U9-Udrp-Ubd : 0;
     554
     555                // Iout linear with U9 above Ubd
     556                //
     557                //  Rx = (U9-Ubd)/Iout
     558                //  I' = (U9'-Ubd) / Rx
     559                //  Udrp' = R*I'
     560                //  Uov = U9' - Udrp' - Ubd
     561                //  Uov = overvoltage
     562                //
     563                //  overvoltage = U9' - Udrp' - Ubd
     564                //  overvoltage = U9' - R*I' - Ubd
     565                //  overvoltage = U9' - R*((U9'-Ubd)/Rx) - Ubd
     566                //  overvoltage = U9' - U9'*R/Rx + Ubd*R/Rx - Ubd
     567                //  overvoltage = U9'*(1 - R/Rx) + Ubd*R/Rx - Ubd
     568                //  overvoltage - Ubd*R/Rx +Ubd = U9'*(1 - R/Rx)
     569                //  U9' = [ overvoltage - Ubd*R/Rx +Ubd ] / (1 - R/Rx)
     570                //
    232571
    233572                // The current through one G-APD is the sum divided by the number of G-APDs
     
    235574                double Iapd = Iout/N;
    236575
    237                 // This is assuming that the broken pixels have a 390 Ohm instead of 3900 Ohm serial resistor
    238576                // In this and the previosu case we neglect the resistance of the G-APDs, but we can make an
    239577                // assumption: The differential resistance depends more on the NSB than on the PDE,
     
    244582                // increase the current flow as compared to the total current flow.
    245583                if (i==66)
    246                     Iapd *= 1.3;
     584                    Iapd /= 1.3;
    247585                if (i==191 || i==193)
    248                     Iapd *= 1.4;
    249 
    250                 // If the G-APD voltage is above the breakdown voltage we have the current through the
    251                 // G-APD and the over-voltage applied to the G-APD to calculate its differential resistor.
    252                 if (Iapd>0)
     586                    Iapd /= 1.4;
     587
     588                // The differential resistance of the G-APD, i.e. the dependence of the
     589                // current above the breakdown voltage, is given by
     590                //const double Rapd = Uov/Iapd;
     591                // This allows us to estimate the current Iov at the overvoltage we want to apply
     592                //const double Iov = pow(overvoltage, 1)/Rapd;
     593
     594                // Estimate set point for over-voltage (voltage drop at the target point)
     595                //const double Uset = Ubd + overvoltage + R*Iov*N;
     596                double Uset = Uov<0.3 ? Ubd + overvoltage + Udrp : Ubd + overvoltage + Udrp*pow(overvoltage/Uov, 1.66);
     597
     598                // Voltage set point
     599                vec[i] = Uset;
     600
     601                // Calculate statistics only for channels with a valid calibration
     602                //if (Uov>0)
    253603                {
    254                     // The differential resistance of the G-APD, i.e. the dependence of the
    255                     // current above the breakdown voltage, is given by
    256                     const double Rapd = Uov/Iapd;
    257 
    258                     // This allows us to estimate the current Iov at the overvoltage we want to apply
    259                     const double Iov = (1.1+fBiasOffset)/Rapd;
    260 
    261                     // Estimate set point for over-voltage
    262                     const double Uset = (1.1+fBiasOffset) + Ubd + R*Iov*N;
    263 
    264                     // Voltage set point as a difference between breakdown voltage and set point
    265                     Uoff[i] = Uset - fBiasVolt[i];
     604                    const int g = hv.group();
     605
     606                    med[g][num[g]] = Uov;
     607                    avg[g] += Uov;
     608                    num[g]++;
     609
     610                    if (Uov<min[g])
     611                        min[g] = Uov;
     612                    if (Uov>max[g])
     613                        max[g] = Uov;
     614
     615                    const double iapd = Iapd*1e6; // A --> uA
     616
     617                    data.I[i]  = iapd;
     618                    data.Iavg += iapd;
     619                    data.Irms += iapd*iapd;
     620
     621                    data.Uov[i] = Uov;
     622
     623                    med[2][num[2]++] = iapd;
    266624                }
    267 
    268                  // Calculate statistics only for channels with a valid calibration
    269                  if (Uov>0)
    270                  {
    271                      const int g = hv.group();
    272 
    273                      med[g][num[g]] = Uov;
    274                      avg[g] += Uov;
    275                      num[g]++;
    276 
    277                      if (Uov<min[g])
    278                          min[g] = Uov;
    279                      if (Uov>max[g])
    280                          max[g] = Uov;
    281                  }
    282625            }
    283 
     626        }
     627
     628        // ------------------------------- Update voltages ------------------------------------
     629
     630        if (GetCurrentState()!=Feedback::State::kCalibrated)
     631        {
     632            DimClient::sendCommandNB("BIAS_CONTROL/SET_ALL_CHANNELS_VOLTAGE",
     633                                     vec.data(), BIAS::kNumChannels*sizeof(float));
     634
     635            ostringstream msg;
     636            msg << setprecision(4) << "Sending new absolute offset: dU(" << fTemp << "degC)=" << fTempOffset << "V, Unom=" << overvoltage << "V, Uov=" << (num[0]+num[1]>0?(avg[0]+avg[1])/(num[0]+num[1]):0);
     637            Info(msg);
     638        }
     639        else
     640        {
     641            ostringstream msg;
     642            msg << setprecision(4) << "Current status: dU(" << fTemp << "degC)=" << fTempOffset << "V, Unom=" << overvoltage << "V, Uov=" << (num[0]+num[1]>0?(avg[0]+avg[1])/(num[0]+num[1]):0);
     643            Info(msg);
     644
     645        }
     646
     647        // --------------------------------- Console out --------------------------------------
     648
     649        if (num[0]>0 && num[1]>0 && fIsVerbose)
     650        {
    284651            sort(med[0].begin(), med[0].begin()+num[0]);
    285652            sort(med[1].begin(), med[1].begin()+num[1]);
    286653
    287             fCurrentsAvg.assign(BIAS::kNumChannels, 0);
    288             fCursorCur = 0;
    289         }
    290 
    291         fDimDeviation.setQuality(fControlType);
    292         fDimDeviation.Update(vec);
    293 
    294         // Warning: Here it is assumed that the ramp up and down is done properly
    295         // within the time between two new temperatures and that the calibration
    296         // is finished within that time.
    297         if (GetCurrentState()!=Feedback::State::kCalibrating ||
    298             fDimBias.state()!=BIAS::State::kVoltageOff ||
    299             fCursorTemp!=1 || !fOutputEnabled)
    300         {
    301             if (!fOutputEnabled || fDimBias.state()!=BIAS::State::kVoltageOn)
    302                 return GetCurrentState();
    303 
    304             // Trigger calibration
    305             if (GetCurrentState()==Feedback::State::kCalibrating && fCursorTemp==2)
    306             {
    307                 DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
    308                 return GetCurrentState();
    309             }
    310         }
    311 
    312         ostringstream msg;
    313         msg << setprecision(4) << "Sending new absolute offset (" << fAppliedOffset << "V+" << (num[0]+num[1]>0?(avg[0]+avg[1])/(num[0]+num[1]):0) << "V) to biasctrl.";
    314         Info(msg);
    315 
    316         if (fControlType==kCurrents && num[0]>0 && num[1]>0)
    317         {
    318             msg.str("");
     654            ostringstream msg;
    319655            msg << "   Avg0=" << setw(7) << avg[0]/num[0]    << "  |  Avg1=" << setw(7) << avg[1]/num[1];
    320656            Debug(msg);
     
    333669        }
    334670
    335         DimClient::sendCommandNB("BIAS_CONTROL/SET_ALL_CHANNELS_OFFSET",
    336                                  vec.data()+BIAS::kNumChannels, BIAS::kNumChannels*sizeof(float));
    337 
    338         return GetCurrentState();
    339     }
    340 
    341     int HandleCurrentControl()
    342     {
    343         const double dUt = fTempOffset; // [V]
    344 
    345         if (GetCurrentState()==Feedback::State::kCalibrating && fBiasOffset>dUt-1.2)
    346         {
    347             fCursorTemp = 0;
    348 
    349             ostringstream msg;
    350             msg << " (applied calibration offset " << fBiasOffset << "V exceeds temperature correction " << fTempOffset << "V - 1.2V.";
    351             Warn("Trying to calibrate above G-APD breakdown volatge!");
    352             Warn(msg);
    353             return GetCurrentState();
    354         }
    355 
    356         // FIXME: If calibrating do not wait for the temperature!
    357         fAppliedOffset = fBiasOffset;
    358         if (GetCurrentState()!=Feedback::State::kCalibrating)
    359             fAppliedOffset += dUt;
    360 
    361         vector<float> vec(2*BIAS::kNumChannels+2);
    362         for (int i=0; i<BIAS::kNumChannels; i++)
    363             vec[i+BIAS::kNumChannels] = fAppliedOffset;
    364 
    365         vec[BIAS::kNumChannels*2]   = dUt;
    366         vec[BIAS::kNumChannels*2+1] = fBiasOffset;
    367 
    368         double avg[2] = {   0,   0 };
    369         double min[2] = {  90,  90 };
    370         double max[2] = { -90, -90 };
    371         int    num[2] = {   0,   0 };
    372 
    373         vector<double> med[2];
    374         med[0].resize(416);
    375         med[1].resize(416);
    376 
    377         if (fControlType==kCurrents)
    378         {
    379             if (fCursorCur==0)
    380             {
    381                 //DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
    382                 return GetCurrentState();
    383             }
    384 
    385             // Pixel  583: 5 31 == 191 (5)  C2 B3 P3
    386             // Pixel  830: 2  2 ==  66 (4)  C0 B8 P1
    387             // Pixel 1401: 6  1 == 193 (5)  C2 B4 P0
    388 
    389             // Convert from DAC counts to uA
    390             const double conv = 5000./4096;
    391 
    392             // 3900 Ohm/n + 1000 Ohm + 1100 Ohm  (with n=4 or n=5)
    393             const double R[2] = { 3075, 2870 };
    394 
    395             const float *Iavg = fCalibration.data();                      // Offset at U=fCalibrationOffset
    396             const float *Ravg = fCalibration.data()+BIAS::kNumChannels*2; // Measured resistance
    397 
    398             // U0 = fCalibrationOffset
    399             // dT = fAppliedVoltage
    400 
    401             // Ifeedback = Im[i] - (U[i]-U0)/Ravg[i] - Iavg[i];
    402             // dUapplied[i] + dUneu[i] = R[g] * (Im[i] - (dUapplied[i]+dUneu[i]-U0+dT)/Ravg[i] - Iavg[i])
    403 
    404             // The assumption here is that the offset calculated from the temperature
    405             // does not significanly change within a single step
    406 
    407             // dU[i] := dUtotal[i] = dUapplied[i] + dUneu[i]
    408             // dU[i] / R[g]               = Im[i] - (dU[i]+dT-U0)/Ravg[i] - Iavg[i]
    409             // dU[i]/R[g] + dU[i]/Ravg[i] = Im[i] + U0/Ravg[i] - dT/Ravg[i] - Iavg[i]
    410             // dU[i]*(1/R[g]+1/Ravg[i])   = Im[i] - Iavg[i] + U0/Ravg[i] - dT/Ravg[i]
    411             // dU[i]   = (Im[i] - Iavg[i] + U0/Ravg[i] - dT/Ravg[i]) / (1/R[g]+1/Ravg[i])
    412             // dU[i] = { Im[i] - Iavg[i] + (U0-dT)/Ravg[i] } * r    with   r := 1 / (1/R[g]+1/Ravg[i])
    413 
    414             const double U0 = fAppliedOffset-fCalibrationOffset;
    415 
    416             for (int i=0; i<BIAS::kNumChannels; i++)
    417             {
    418                 const PixelMapEntry &hv = fMap.hv(i);
    419                 if (!hv)
    420                     continue;
    421 
    422                 // Average measured current
    423                 const double Im = double(fCurrentsAvg[i])/fCursorCur * conv; // [uA]
    424 
    425                 // Group index (0 or 1) of the of the pixel (4 or 5 pixel patch)
    426                 const int g = hv.group();
    427 
    428                 // Serial resistors in front of the G-APD
    429                 double Rg = R[g];
    430 
    431                 // This is assuming that the broken pixels have a 390 Ohm instead of 3900 Ohm serial resistor
    432                 if (i==66)                // Pixel 830(66)
    433                     Rg = 2400;            // 2400 = (3/3900 + 1/390) + 1000 + 1100
    434                 if (i==191 || i==193)     // Pixel 583(191) / Pixel 1401(193)
    435                     Rg = 2379;            // 2379 = (4/3900 + 1/390) + 1000 + 1100
    436 
    437                 const double r = 1./(1./Rg + 1./Ravg[i]); // [Ohm]
    438 
    439                 // Offset induced by the voltage above the calibration point
    440                 const double dI = U0/Ravg[i]; // [V/Ohm]
    441  
    442                 // Offset at the calibration point (make sure that the calibration is
    443                 // valid (Im[i]>Iavg[i]) and we operate above the calibration point)
    444                 const double I = Im>Iavg[i] ? Im - Iavg[i] : 0; // [A]
    445 
    446                 // Make sure that the averaged resistor is valid
    447                 const double dU = Ravg[i]>10000 ? r*(I*1e-6 - dI) : 0;
    448 
    449                 vec[i+BIAS::kNumChannels] += dU;
    450 
    451                 // Angelegte Spannung:    U0+dU
    452                 // Gemessener Strom:      Im - Iavg
    453                 // Strom offset:          (U0+dU) / Ravg
    454                 // Fliessender Strom:     Im-Iavg - (U0+dU)/Ravg
    455                 // Korrektur:             [ Im-Iavg - (U0+dU)/Ravg ] * Rg
    456 
    457                 // Aufgeloest nach dU:    dU =  ( Im-Iavg - dU/Ravg ) / ( 1/Rg + 1/Ravg )
    458                 // Equivalent zu:         dU  = ( I*Ravg - U0 ) / ( Ravg/Rg+1 )
    459 
    460                 // Calculate statistics only for channels with a valid calibration
    461                 if (Iavg[i]>0)
    462                 {
    463                     med[g][num[g]] = dU;
    464                     avg[g] += dU;
    465                     num[g]++;
    466 
    467                     if (dU<min[g])
    468                         min[g] = dU;
    469                     if (dU>max[g])
    470                         max[g] = dU;
    471                 }
    472             }
    473 
    474             sort(med[0].begin(), med[0].begin()+num[0]);
    475             sort(med[1].begin(), med[1].begin()+num[1]);
    476 
    477             fCurrentsAvg.assign(BIAS::kNumChannels, 0);
    478             fCursorCur = 0;
    479         }
    480 
    481         fDimDeviation.setQuality(fControlType);
    482         fDimDeviation.Update(vec);
    483 
    484         // Warning: Here it is assumed that the ramp up and down is done properly
    485         // within the time between two new temperatures and that the calibration
    486         // is finished within that time.
    487         if (!(GetCurrentState()==Feedback::State::kCalibrating && fCursorTemp==1 && fOutputEnabled && fDimBias.state()==BIAS::State::kVoltageOff))
    488         {
    489             if (!fOutputEnabled || fDimBias.state()!=BIAS::State::kVoltageOn)
    490                 return GetCurrentState();
    491 
    492             // Trigger calibration
    493             if (GetCurrentState()==Feedback::State::kCalibrating && fCursorTemp==2)
    494             {
    495                 DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
    496                 return GetCurrentState();
    497             }
    498         }
    499 
    500         ostringstream msg;
    501         msg << setprecision(4) << "Sending new absolute offset (" << fAppliedOffset << "V+" << (num[0]+num[1]>0?(avg[0]+avg[1])/(num[0]+num[1]):0) << "V) to biasctrl.";
    502         Info(msg);
    503 
    504         if (fControlType==kCurrents && num[0]>0 && num[1]>0)
    505         {
    506             msg.str("");
    507             msg << "   Avg0=" << setw(7) << avg[0]/num[0]    << "  |  Avg1=" << setw(7) << avg[1]/num[1];
    508             Debug(msg);
    509 
    510             msg.str("");
    511             msg << "   Med0=" << setw(7) << med[0][num[0]/2] << "  |  Med1=" << setw(7) << med[1][num[1]/2];
    512             Debug(msg);
    513 
    514             msg.str("");
    515             msg << "   Min0=" << setw(7) << min[0]           << "  |  Min1=" << setw(7) << min[1];
    516             Debug(msg);
    517 
    518             msg.str("");
    519             msg << "   Max0=" << setw(7) << max[0]           << "  |  Max1=" << setw(7) << max[1];
    520             Debug(msg);
    521         }
    522 
    523         DimClient::sendCommandNB("BIAS_CONTROL/SET_ALL_CHANNELS_OFFSET",
    524                                  vec.data()+BIAS::kNumChannels, BIAS::kNumChannels*sizeof(float));
    525 
    526         return GetCurrentState();
    527     }
    528 
    529     int AverageCurrents(const EventImp &evt)
    530     {
    531         if (evt.GetSize()!=BIAS::kNumChannels*sizeof(int16_t))
    532             return -1;
    533 
    534         if (fDimBias.state()!=BIAS::State::kVoltageOn)
    535             return false;
    536 
    537         if (fCursorCur++<0)
    538             return true;
    539 
    540         const int16_t *ptr = evt.Ptr<int16_t>();
    541 
    542         for (int i=0; i<BIAS::kNumChannels; i++)
    543         {
    544             fCurrentsAvg[i] += ptr[i];
    545             fCurrentsRms[i] += ptr[i]*ptr[i];
    546         }
    547 
    548         return true;
    549     }
    550 
    551 
    552     void HandleCalibration(const EventImp &evt)
    553     {
    554         const int rc = AverageCurrents(evt);
    555         if (rc<0)
    556             return;
    557 
    558         if (fCursorCur<fNumCalibRequests)
    559         {
    560             if (fDimBias.state()==BIAS::State::kVoltageOn)
    561                 DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
    562             return;
    563         }
    564 
    565         if (rc==0)
    566             return;
    567 
    568         fCalibration.resize(BIAS::kNumChannels*3);
    569 
    570         float *avg = fCalibration.data();
    571         float *rms = fCalibration.data()+BIAS::kNumChannels;
    572         float *res = fCalibration.data()+BIAS::kNumChannels*2;
    573 
    574         const double conv = 5000./4096;
    575 
    576         for (int i=0; i<BIAS::kNumChannels; i++)
    577         {
    578             const double I = double(fCurrentsAvg[i])/fCursorCur;
    579 
    580             res[i] = (fVoltGapd[i]+fCalibrationOffset)/I / conv * 1e6;
    581             avg[i] = conv * I;
    582             rms[i] = conv * sqrt(double(fCurrentsRms[i])/fCursorCur-I*I);
    583         }
    584 
    585         fCalibTime = Time();
    586 
    587         fDimCalibration.setData(fCalibration);
    588         fDimCalibration.Update(fCalibTime);
    589 
    590         fOutputEnabled = false;
    591         fControlType = kIdle;
    592 
    593         Info("Calibration successfully done.");
    594 
    595         if (fDimBias.state()==BIAS::State::kVoltageOn)
    596             DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
    597     }
    598 
    599     void HandleFeedback(const EventImp &evt)
    600     {
    601         if (evt.GetSize()!=1440*sizeof(float))
    602             return;
    603 
    604         // -------- Check age of last stored event --------
    605 
    606         const Time tm(evt.GetTime());
    607 
    608         if (Time()-fBiasLast>boost::posix_time::seconds(30))
    609         {
    610             Warn("Last received event data older than 30s... resetting average calculation.");
    611             ResetData();
    612         }
    613         fBiasLast = tm;
    614 
    615         // -------- Store new event --------
    616 
    617         fData[fCursorAmpl%fData.size()].assign(evt.Ptr<float>(), evt.Ptr<float>()+1440);
    618         if (++fCursorAmpl<fData.size())
    619             return;
    620 
    621         // -------- Calculate statistics --------
    622 
    623         valarray<double> med(1440);
    624 
    625         for (int ch=0; ch<1440; ch++)
    626         {
    627             vector<float> arr(fData.size());
    628             for (size_t i=0; i<fData.size(); i++)
    629                 arr[i] = fData[i][ch];
    630 
    631             sort(arr.begin(), arr.end());
    632 
    633             med[ch] = arr[arr.size()/2];
    634         }
    635 
    636         /*
    637             vector<float> med(1440);
    638             vector<float> rms(1440);
    639             for (size_t i=0; i<fData.size(); i++)
    640             {
    641                 if (fData[i].size()==0)
    642                     return;
    643 
    644                 for (int j=0; j<1440; j++)
    645                 {
    646                     med[j] += fData[i][j];
    647                     rms[j] += fData[i][j]*fData[i][j];
    648                 }
    649             }
    650             */
    651 
    652         vector<double> avg(BIAS::kNumChannels);
    653         vector<int>    num(BIAS::kNumChannels);
    654         for (int i=0; i<1440; i++)
    655         {
    656             const PixelMapEntry &ch = fMap.hw(i);
    657 
    658             // FIXME: Add a consistency check if the median makes sense...
    659             // FIXME: Add a consistency check to remove pixels with bright stars (median?)
    660 
    661             avg[ch.hv()] += med[i];
    662             num[ch.hv()]++;
    663         }
    664 
    665         for (int i=0; i<BIAS::kNumChannels; i++)
    666         {
    667             if (num[i])
    668                 avg[i] /= num[i];
    669 
    670         }
    671 
    672         // -------- Calculate correction --------
    673 
    674         // http://bestune.50megs.com/typeABC.htm
    675 
    676         // CO: Controller output
    677         // PV: Process variable
    678         // SP: Set point
    679         // T:  Sampling period (loop update period)
    680         // e = SP - PV
    681         //
    682         // Kp : No units
    683         // Ki : per seconds
    684         // Kd : seconds
    685 
    686         // CO(k)-CO(k-1) = - Kp[ PV(k) - PV(k-1) ] + Ki * T * (SP(k)-PV(k)) - Kd/T [ PV(k) - 2PV(k-1) + PV(k-2) ]
    687 
    688         if (fCursorAmpl%fData.size()>0)
    689             return;
    690 
    691         // FIXME: Take out broken / dead boards.
    692 
    693         const Time tm0 = Time();
    694 
    695         /*const*/ double T21 = fT>0 ? fT : (tm0-fStartTime).total_microseconds()/1000000.;
    696         const double T10 = fT21;
    697         fT21 = T21;
    698 
    699         fStartTime = tm0;
    700 
    701         ostringstream out;
    702         out << "New " << fData.size() << " event received: " << fCursorAmpl << " / " << setprecision(3) << T21 << "s";
    703         Info(out);
    704 
    705         if (fPV[0].size()==0)
    706         {
    707             fPV[0].resize(avg.size());
    708             fPV[0] = valarray<double>(avg.data(), avg.size());
    709             return;
    710         }
    711 
    712         if (fPV[1].size()==0)
    713         {
    714             fPV[1].resize(avg.size());
    715             fPV[1] = valarray<double>(avg.data(), avg.size());
    716             return;
    717         }
    718 
    719         if (fPV[2].size()==0)
    720         {
    721             fPV[2].resize(avg.size());
    722             fPV[2] = valarray<double>(avg.data(), avg.size());
    723             return;
    724         }
    725 
    726         fPV[0] = fPV[1];
    727         fPV[1] = fPV[2];
    728 
    729         fPV[2].resize(avg.size());
    730         fPV[2] = valarray<double>(avg.data(), avg.size());
    731 
    732         if (T10<=0 || T21<=0)
    733             return;
    734 
    735         //cout << "Calculating (" << fCursor << ":" << T21 << ")... " << endl;
    736 
    737         // fKi[j] = response[j]*gain;
    738         // Kp = 0;
    739         // Kd = 0;
    740 
    741         // => Kp = 0.01 * gain     = 0.00005
    742         // => Ki = 0.8  * gain/20s = 0.00025
    743         // => Kd = 0.1  * gain/20s = 0.00003
    744 
    745         /*
    746         fKp = 0;
    747         fKd = 0;
    748         fKi = 0.00003*20;
    749         T21 = 1;
    750         */
    751 
    752         //valarray<double> correction = - Kp*(PV[2] - PV[1]) + Ki * dT * (SP-PV[2]) - Kd/dT * (PV[2] - 2*PV[1] + PV[0]);
    753         //valarray<double> correction =
    754         // -      Kp * (PV[2] - PV[1])
    755         // + dT * Ki * (SP    - PV[2])
    756         // - Kd / dT * (PV[2] - 2*PV[1] + PV[0]);
    757         //
    758         // - (Kp+Kd/dT1) * (PV[2] - PV[1])
    759         // + dT2 * Ki * (SP    - PV[2])
    760         // + Kd / dT1 * (PV[1] - PV[0]);
    761         //
    762         // - Kp * (PV[2] - PV[1])
    763         // + Ki * (SP    - PV[2])*dT
    764         // - Kd * (PV[2] - PV[1])/dT
    765         // + Kd * (PV[1] - PV[0])/dT;
    766         //
    767         //valarray<double> correction =
    768         //    - Kp*(PV[2] - PV[1]) + Ki * T21 * (SP-PV[2]) - Kd*(PV[2]-PV[1])/T21 - Kd*(PV[0]-PV[1])/T01;
    769         const valarray<double> correction = 1./fGain/1000*
    770             (
    771              - (fKp+fKd/T21)*(fPV[2] - fPV[1])
    772              +  fKi*T21*(fSP-fPV[2])
    773              +  fKd/T10*(fPV[1]-fPV[0])
    774             );
    775 
    776         /*
    777          integral = 0
    778          start:
    779          integral += (fSP - fPV[2])*dt
    780 
    781          output = Kp*(fSP - fPV[2]) + Ki*integral - Kd*(fPV[2] - fPV[1])/dt
    782 
    783          wait(dt)
    784 
    785          goto start
    786          */
    787 
    788         vector<float> vec(2*BIAS::kNumChannels+2);
    789         for (int i=0; i<BIAS::kNumChannels; i++)
    790             vec[i] = fPV[2][i]-fSP[i];
    791 
    792         for (int i=0; i<BIAS::kNumChannels; i++)
    793             vec[i+BIAS::kNumChannels] = avg[i]<5*2.5 ? 0 : correction[i];
    794 
    795         fDimDeviation.setQuality(fControlType);
    796         fDimDeviation.Update(vec);
    797 
    798         if (!fOutputEnabled || fDimBias.state()!=BIAS::State::kVoltageOn)
    799             return;
    800 
    801         Info("Sending new relative offset to biasctrl.");
    802 
    803         DimClient::sendCommandNB("BIAS_CONTROL/INCREASE_ALL_CHANNELS_VOLTAGE",
    804                                  vec.data()+BIAS::kNumChannels, BIAS::kNumChannels*sizeof(float));
    805     }
    806 
    807     void HandleGlobalFeedback(const EventImp &evt)
    808     {
    809         if (evt.GetSize()!=1440*sizeof(float))
    810             return;
    811 
    812         // -------- Store new event --------
    813 
    814         vector<float> arr(evt.Ptr<float>(), evt.Ptr<float>()+1440);
    815 
    816         sort(arr.begin(), arr.end());
    817 
    818         const float med = arr[arr.size()/2];
    819 
    820         fData[fCursorAmpl%fData.size()].resize(1); //assign(&med, &med);
    821         fData[fCursorAmpl%fData.size()][0] = med;  //assign(&med, &med);
    822 
    823         if (++fCursorAmpl<fData.size())
    824             return;
    825 
    826         // -------- Calculate statistics --------
    827 
    828         double avg=0;
    829         double rms=0;
    830         for (size_t i=0; i<fData.size(); i++)
    831         {
    832             avg += fData[i][0];
    833             rms += fData[i][0]*fData[i][0];
    834         }
    835 
    836         avg /= fData.size();
    837         rms /= fData.size();
    838 
    839         rms  = sqrt(rms-avg*avg);
    840 
    841         // -------- Calculate correction --------
    842 
    843         if (fCursorAmpl%fData.size()!=0)
    844             return;
    845 
    846         Out() << "Amplitude: " << avg << " +- " << rms << endl;
    847 
    848         // FIXME: Take out broken / dead boards.
    849 
    850         /*
    851         ostringstream out;
    852         out << "New " << fData.size() << " event received: " << fCursor << " / " << setprecision(3) << T21 << "s";
    853         Info(out);
    854         */
    855 
    856         if (fPV[0].size()==0)
    857         {
    858             fPV[0].resize(1);
    859             fPV[0] = valarray<double>(&avg, 1);
    860             return;
    861         }
    862 
    863         if (fPV[1].size()==0)
    864         {
    865             fPV[1].resize(1);
    866             fPV[1] = valarray<double>(&avg, 1);
    867             return;
    868         }
    869 
    870         if (fPV[2].size()==0)
    871         {
    872             fPV[2].resize(1);
    873             fPV[2] = valarray<double>(&avg, 1);
    874             return;
    875         }
    876 
    877         fPV[0] = fPV[1];
    878         fPV[1] = fPV[2];
    879 
    880         fPV[2].resize(1);
    881         fPV[2] = valarray<double>(&avg, 1);
    882 
    883         // ----- Calculate average currents -----
    884 
    885         vector<float> A(BIAS::kNumChannels);
    886         for (int i=0; i<BIAS::kNumChannels; i++)
    887             A[i] = double(fCurrentsAvg[i]) / fCursorCur;
    888 
    889         fCurrentsAvg.assign(BIAS::kNumChannels, 0);
    890         fCursorCur = 0;
    891 
    892         // -------- Calculate correction --------
    893 
    894         // correction = (fSP[0]-fPV[2])*fKi
    895         /*
    896         const double T21 = 1; // feedback is  1s
    897         const double T10 = 1; // feedback is 20s
    898 
    899         const valarray<double> correction = 1./fGain/1000*
    900             (
    901              - (fKp+fKd/T21)*(fPV[2] - fPV[1])
    902              +  fKi*T21*(fSP[0]-fPV[2])
    903              +  fKd/T10*(fPV[1]-fPV[0])
    904             );
    905         */
    906 
    907         // pow of 1.6 comes from the non-linearity of the
    908         // amplitude vs bias voltage
    909         const valarray<double> correction = 1./fGain/1000*
    910             (
    911              //fKi*(pow(fSP[0], 1./1.6)-pow(fPV[2], 1./1.6))
    912              fKi*(fSP[0]-fPV[2])
    913             );
    914 
    915         Out() << "Correction: " << correction[0] << "V (" << fSP[0] << ")" << endl;
    916 
    917         const int nch = BIAS::kNumChannels;
    918 
    919         // FIXME: Sanity check!
    920 
    921         vector<float> vec;
    922         vec.reserve(2*nch+2);
    923         vec.insert(vec.begin(),     nch, fPV[2][0]-fSP[0]);
    924         vec.insert(vec.begin()+nch, nch, correction[0]);
    925         vec.push_back(0);
    926         vec.push_back(0);
    927 
    928         fDimDeviation.setQuality(fControlType);
    929         fDimDeviation.Update(vec);
    930 
    931         if (!fOutputEnabled || fDimBias.state()!=BIAS::State::kVoltageOn)
    932             return;
    933 
    934         Info("Sending new global relative offset to biasctrl.");
    935 
    936         DimClient::sendCommandNB("BIAS_CONTROL/INCREASE_ALL_CHANNELS_VOLTAGE",
    937                                  vec.data()+BIAS::kNumChannels, BIAS::kNumChannels*sizeof(float));
    938     }
    939 
    940     void HandleCalibrateCurrents(const EventImp &evt)
    941     {
    942         if (fBiasVolt.empty() || fCalibration.empty() || evt.GetSize()<416*sizeof(int16_t))
    943             return;
    944 
    945         struct dim_data {
    946             float I[416];
    947             float Iavg;
    948             float Irms;
    949             float Imed;
    950             float Idev;
    951             uint32_t N;
    952             float Tdiff;
    953 
    954             dim_data() { memset(this, 0, sizeof(dim_data)); }
    955         } __attribute__((__packed__));
    956 
    957         const int16_t *I = evt.Ptr<int16_t>();
    958         const float   *R = fCalibration.data()+BIAS::kNumChannels*2;
    959         const float   *U = fBiasVolt.data();
    960 
    961         vector<float> med(416);
    962         uint16_t cnt = 0;
    963 
    964         double avg = 0;
    965         double rms = 0;
    966 
    967         dim_data data;
    968         for (int i=0; i<416; i++)
    969         {
    970             const PixelMapEntry &hv = fMap.hv(i);
    971             if (!hv)
    972                 continue;
    973 
    974             if (R[i]<=0)
    975                 continue;
    976 
    977             data.I[i]  = I[i]*5000./4096 - U[i]/R[i]*1e6;
    978             data.I[i] /= hv.group() ? 5 : 4;
    979 
    980             avg += data.I[i];
    981             rms += data.I[i]*data.I[i];
    982 
    983             if (i>=320)
    984                 continue;
    985 
    986             med[cnt++] = data.I[i];
    987         }
    988 
    989         if (cnt==0)
    990             return;
    991 
    992         avg /= cnt;
    993         rms /= cnt;
    994 
    995         data.N = cnt;
    996         data.Iavg = avg;
    997         data.Irms = sqrt(rms-avg*avg);
    998 
    999         sort(med.data(), med.data()+cnt);
    1000 
    1001         data.Imed = cnt%2 ? (med[cnt/2-1]+med[cnt/2])/2 : med[cnt/2];
    1002 
    1003         for (int i=0; i<cnt; i++)
    1004             med[i] = fabs(med[i]-data.Imed);
    1005 
    1006         sort(med.data(), med.data()+cnt);
    1007 
    1008         data.Idev = med[uint32_t(0.682689477208650697*cnt)];
    1009 
    1010         data.Tdiff = evt.GetTime().UnixTime()-fCalibTime.UnixTime();
    1011 
    1012         fDimCurrents.setData(&data, sizeof(dim_data));
    1013         fDimCurrents.Update(evt.GetTime());
    1014     }
    1015 
    1016     int HandleBiasCurrent(const EventImp &evt)
    1017     {
    1018         if (fControlType==kTemp && GetCurrentState()==Feedback::State::kCalibrating)
    1019             HandleCalibration(evt);
    1020 
    1021         if (fControlType==kFeedbackGlobal || fControlType==kCurrents || fControlType==kCurrentsNew)
    1022             AverageCurrents(evt);
    1023 
    1024         /*
    1025         if (fControlType==kCurrents && fCursorTemp>0 && fCursorCur>0)
    1026         {
    1027             // fCursorTemp: 1 2 3 4 5 6 7 8
    1028             // fCursor%x:   1 1 1 2 2 2 3 3    // 9 steps in ~15s
    1029             //if (fCursorTemp<3 && fCursorCur%(fCursorTemp/3+1)==0)
    1030                 HandleCurrentControl();
    1031         }*/
    1032 
    1033         HandleCalibrateCurrents(evt);
    1034 
    1035         return GetCurrentState();
    1036     }
    1037 
    1038     int HandleBiasData(const EventImp &evt)
    1039     {
    1040         if (fControlType==kFeedback)
    1041             HandleFeedback(evt);
    1042 
    1043         if (fControlType==kFeedbackGlobal)
    1044             HandleGlobalFeedback(evt);
    1045 
    1046         return GetCurrentState();
    1047     }
    1048 
    1049     int HandleBiasNom(const EventImp &evt)
    1050     {
    1051         if (evt.GetSize()>=416*sizeof(float))
    1052         {
    1053             fVoltGapd.assign(evt.Ptr<float>(), evt.Ptr<float>()+416);
    1054             Info("Nominal bias voltages received.");
    1055         }
    1056 
    1057         return GetCurrentState();
    1058     }
    1059 
    1060     int HandleBiasVoltage(const EventImp &evt)
    1061     {
    1062         if (evt.GetSize()>=416*sizeof(float))
    1063             fBiasVolt.assign(evt.Ptr<float>(), evt.Ptr<float>()+416);
    1064         return GetCurrentState();
    1065     }
    1066 
    1067     bool CheckEventSize(size_t has, const char *name, size_t size)
    1068     {
    1069         if (has==size)
    1070             return true;
    1071 
    1072         ostringstream msg;
    1073         msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
    1074         Fatal(msg);
    1075         return false;
    1076     }
     671        // ---------------------------- Calibrated Currents -----------------------------------
     672
     673        if (num[2]>0)
     674        {
     675            data.Iavg /= num[2];
     676            data.Irms /= num[2];
     677
     678            data.N = num[2];
     679            data.Irms = sqrt(data.Irms-data.Iavg*data.Iavg);
     680
     681            sort(med[2].data(), med[2].data()+num[2]);
     682
     683            data.Imed = num[2]%2 ? (med[2][num[2]/2-1]+med[2][num[2]/2])/2 : med[2][num[2]/2];
     684
     685            for (int i=0; i<num[2]; i++)
     686                med[2][i] = fabs(med[2][i]-data.Imed);
     687
     688            sort(med[2].data(), med[2].data()+num[2]);
     689
     690            data.Idev = med[2][uint32_t(0.682689477208650697*num[2])];
     691
     692            data.Tdiff = evt.GetTime().UnixTime()-fTimeCalib.UnixTime();
     693
     694            // FIXME:
     695            //  + Current overvoltage
     696            //  + Temp offset
     697            //  + User offset
     698            //  + Command overvoltage
     699            fDimCurrents.setQuality(GetCurrentState());
     700            fDimCurrents.setData(&data, sizeof(dim_data));
     701            fDimCurrents.Update(evt.GetTime());
     702        }
     703
     704        return GetCurrentState()==Feedback::State::kCalibrated ? Feedback::State::kCalibrated : Feedback::State::kInProgress;
     705    }
     706
     707    // ======================================================================
    1077708
    1078709    int Print() const
    1079710    {
    1080711        Out() << fDim << endl;
    1081         Out() << fDimFAD << endl;
    1082712        Out() << fDimFSC << endl;
    1083713        Out() << fDimBias << endl;
     
    1088718    int PrintCalibration()
    1089719    {
    1090         if (fCalibration.empty())
     720        /*
     721        if (fCalibration.size()==0)
    1091722        {
    1092723            Out() << "No calibration performed so far." << endl;
     
    1121752
    1122753        Out() << flush;
    1123 
     754        */
    1124755        return GetCurrentState();
    1125756    }
    1126757
    1127     void WarnState(bool needfsc, bool needfad)
    1128     {
     758    int EnableOldAlgorithm(const EventImp &evt)
     759    {
     760        if (!CheckEventSize(evt.GetSize(), "EnableOldAlgorithm", 1))
     761            return kSM_FatalError;
     762
     763        fEnableOldAlgorithm = evt.GetBool();
     764
     765        return GetCurrentState();
     766    }
     767
     768    int SetVerbosity(const EventImp &evt)
     769    {
     770        if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
     771            return kSM_FatalError;
     772
     773        fIsVerbose = evt.GetBool();
     774
     775        return GetCurrentState();
     776    }
     777
     778    int SetCurrentRequestInterval(const EventImp &evt)
     779    {
     780        if (!CheckEventSize(evt.GetSize(), "SetCurrentRequestInterval", 2))
     781            return kSM_FatalError;
     782
     783        fCurrentRequestInterval = evt.GetUShort();
     784
     785        Out() << "New current request interval: " << fCurrentRequestInterval << "ms" << endl;
     786
     787        return GetCurrentState();
     788    }
     789
     790    int Calibrate()
     791    {
     792        if (fDimBias.state()!=BIAS::State::kVoltageOff)
     793        {
     794            Warn("Calibration can only be started when biasctrl is in state VoltageOff.");
     795            return GetCurrentState();
     796        }
     797
     798        Message("Starting calibration (ignore="+to_string(fNumCalibIgnore)+", N="+to_string(fNumCalibRequests)+")");
     799
     800        fCursorCur  = -fNumCalibIgnore;
     801        fCurrentsAvg.assign(BIAS::kNumChannels, 0);
     802        fCurrentsRms.assign(BIAS::kNumChannels, 0);
     803
     804        fBiasDac.assign(BIAS::kNumChannels, 0);
     805
     806        fCalibStep = 3;
     807        fTimeCalib = Time();
     808
     809        Dim::SendCommandNB("BIAS_CONTROL/SET_GLOBAL_DAC", uint32_t(256+512*fCalibStep));
     810
     811        return Feedback::State::kCalibrating;
     812    }
     813
     814    int Start(const EventImp &evt)
     815    {
     816        if (!CheckEventSize(evt.GetSize(), "Start", 4))
     817            return kSM_FatalError;
     818
     819        fUserOffset = evt.GetFloat();
     820
     821        fCursorCur = 0;
     822
     823        fCurrentsAvg.assign(BIAS::kNumChannels, 0);
     824        fCurrentsRms.assign(BIAS::kNumChannels, 0);
     825
     826        ostringstream out;
     827        out << "Starting feedback with an offset of " << fUserOffset << "V";
     828        Message(out);
     829
     830        return Feedback::State::kWaitingForData;
     831    }
     832
     833    int StopFeedback()
     834    {
     835        if (GetCurrentState()==Feedback::State::kCalibrating)
     836            return Feedback::State::kConnected;
     837
     838        if (GetCurrentState()>Feedback::State::kCalibrated)
     839            return Feedback::State::kCalibrated;
     840
     841        return GetCurrentState();
     842    }
     843
     844    int Execute()
     845    {
     846        if (!fDim.online())
     847            return Feedback::State::kDimNetworkNA;
     848
    1129849        const bool bias = fDimBias.state() >= BIAS::State::kConnecting;
    1130850        const bool fsc  = fDimFSC.state()  >= FSC::State::kConnected;
    1131         const bool fad  = fDimFAD.state()  >= FAD::State::kConnected;
    1132 
    1133         if (!bias)
    1134             Warn("Bias control not yet ready.");
    1135         if (needfsc && !fsc)
    1136             Warn("FSC control not yet ready.");
    1137         if (needfad && !fad)
    1138             Warn("FAD control not yet ready.");
    1139     }
    1140 
    1141     int SetConstant(const EventImp &evt, int constant)
    1142     {
    1143         if (!CheckEventSize(evt.GetSize(), "SetConstant", 8))
    1144             return kSM_FatalError;
    1145 
    1146         switch (constant)
    1147         {
    1148         case 0: fKi   = evt.GetDouble(); break;
    1149         case 1: fKp   = evt.GetDouble(); break;
    1150         case 2: fKd   = evt.GetDouble(); break;
    1151         case 3: fT    = evt.GetDouble(); break;
    1152         case 4: fGain = evt.GetDouble(); break;
    1153         default:
    1154             Fatal("SetConstant got an unexpected constant id -- this is a program bug!");
    1155             return kSM_FatalError;
     851
     852        // All subsystems are not connected
     853        if (!bias && !fsc)
     854            return Feedback::State::kDisconnected;
     855
     856        // Not all subsystems are yet connected
     857        if (!bias || !fsc)
     858            return Feedback::State::kConnecting;
     859
     860        if (GetCurrentState()<Feedback::State::kCalibrating)
     861            return Feedback::State::kConnected;
     862
     863        if (GetCurrentState()==Feedback::State::kConnected)
     864            return GetCurrentState();
     865        if (GetCurrentState()==Feedback::State::kCalibrating)
     866            return GetCurrentState();
     867
     868        // kCalibrated, kWaitingForData, kInProgress
     869
     870        if (fDimBias.state()==BIAS::State::kVoltageOn || (fDimBias.state()==BIAS::State::kVoltageOff && GetCurrentState()==Feedback::State::kWaitingForData))
     871        {
     872            static Time past;
     873            if (fCurrentRequestInterval>0 && Time()-past>boost::posix_time::milliseconds(fCurrentRequestInterval))
     874            {
     875                Dim::SendCommandNB("BIAS_CONTROL/REQUEST_STATUS");
     876                past = Time();
     877            }
    1156878        }
    1157879
    1158880        return GetCurrentState();
    1159     }
    1160 
    1161     int EnableOutput(const EventImp &evt)
    1162     {
    1163         if (!CheckEventSize(evt.GetSize(), "EnableOutput", 1))
    1164             return kSM_FatalError;
    1165 
    1166         fOutputEnabled = evt.GetBool();
    1167 
    1168         if (fControlType==kCurrents || fControlType==kCurrentsNew)
    1169             if (fCursorTemp>1)
    1170                 fCursorTemp = 1;
    1171 
    1172         return GetCurrentState();
    1173     }
    1174 
    1175     void ResetData(int16_t n=-1)
    1176     {
    1177         fData.assign(n>0 ? n : fData.size(), vector<float>(0));
    1178 
    1179         fCursorAmpl = 0;
    1180         fCursorCur  = 0;
    1181         fCursorTemp = 0;
    1182 
    1183         fStartTime = Time();
    1184 
    1185         fSP = valarray<double>(0., BIAS::kNumChannels);
    1186 
    1187         vector<float> vec(2*BIAS::kNumChannels+2, fBiasOffset);
    1188         vec[2*BIAS::kNumChannels]   = 0;
    1189         fDimDeviation.setQuality(kIdle);
    1190         fDimDeviation.Update(vec);
    1191 
    1192         fPV[0].resize(0);
    1193         fPV[1].resize(0);
    1194         fPV[2].resize(0);
    1195 
    1196         fCurrentsAvg.assign(BIAS::kNumChannels, 0);
    1197         fCurrentsRms.assign(BIAS::kNumChannels, 0);
    1198 
    1199         if (fKp==0 && fKi==0 && fKd==0)
    1200             Warn("Control loop parameters are all set to zero.");
    1201     }
    1202 
    1203     int StartFeedback(const EventImp &evt)
    1204     {
    1205         if (!CheckEventSize(evt.GetSize(), "StartFeedback", 2))
    1206             return kSM_FatalError;
    1207 
    1208         WarnState(false, true);
    1209 
    1210         fBiasOffset = 0;
    1211         ResetData(evt.GetShort());
    1212 
    1213         fControlType = kFeedback;
    1214 
    1215         return GetCurrentState();
    1216     }
    1217 
    1218     int StartFeedbackGlobal(const EventImp &evt)
    1219     {
    1220         if (!CheckEventSize(evt.GetSize(), "StartFeedbackGlobal", 2))
    1221             return kSM_FatalError;
    1222 
    1223         WarnState(false, true);
    1224 
    1225         fBiasOffset = 0;
    1226         ResetData(evt.GetShort());
    1227 
    1228         fControlType = kFeedbackGlobal;
    1229 
    1230         return GetCurrentState();
    1231     }
    1232 
    1233     int StartTempCtrl(const EventImp &evt)
    1234     {
    1235         if (!CheckEventSize(evt.GetSize(), "StartTempCtrl", 4))
    1236             return kSM_FatalError;
    1237 
    1238         WarnState(true, false);
    1239 
    1240         fBiasOffset = evt.GetFloat();
    1241         fControlType = kTemp;
    1242 
    1243         ostringstream out;
    1244         out << "Starting temperature feedback with an offset of " << fBiasOffset << "V";
    1245         Message(out);
    1246 
    1247         if (fDimBias.state()==BIAS::State::kVoltageOn)
    1248             DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
    1249 
    1250         return GetCurrentState();
    1251     }
    1252 
    1253     int StartCurrentCtrl(const EventImp &evt)
    1254     {
    1255         if (!CheckEventSize(evt.GetSize(), "StartCurrentCtrl", 4))
    1256             return kSM_FatalError;
    1257 
    1258         if (fCalibration.empty())
    1259         {
    1260             Warn("Current control needs a bias crate calibration first... command ignored.");
    1261             return GetCurrentState();
    1262         }
    1263 
    1264         WarnState(true, false);
    1265 
    1266         fBiasOffset = evt.GetFloat();
    1267         fTempOffset = -3;
    1268         ResetData(0);
    1269         fControlType = kCurrents;
    1270 
    1271         ostringstream out;
    1272         out << "Starting current/temp feedback with an offset of " << fBiasOffset << "V";
    1273         Message(out);
    1274 
    1275         return GetCurrentState();
    1276     }
    1277 
    1278     int StartNewCurrentCtrl(const EventImp &evt)
    1279     {
    1280         if (!CheckEventSize(evt.GetSize(), "StartNewCurrentCtrl", 4))
    1281             return kSM_FatalError;
    1282 
    1283         if (fCalibration.empty())
    1284         {
    1285             Warn("Current control needs a bias crate calibration first... command ignored.");
    1286             return GetCurrentState();
    1287         }
    1288 
    1289         WarnState(true, false);
    1290 
    1291         fBiasOffset = evt.GetFloat();
    1292         fTempOffset = -3;
    1293         ResetData(0);
    1294         fControlType = kCurrentsNew;
    1295 
    1296         ostringstream out;
    1297         out << "Starting new current/temp feedback with an offset of " << fBiasOffset << "V";
    1298         Message(out);
    1299 
    1300         return GetCurrentState();
    1301     }
    1302 
    1303     int StopFeedback()
    1304     {
    1305         fControlType = kIdle;
    1306 
    1307         return GetCurrentState();
    1308     }
    1309 
    1310     int StoreReference()
    1311     {
    1312         if (!fPV[0].size() && !fPV[1].size() && !fPV[2].size())
    1313         {
    1314             Warn("No values in memory. Take enough events first!");
    1315             return GetCurrentState();
    1316         }
    1317 
    1318         // FIXME: Check age
    1319 
    1320         if (!fPV[1].size() && !fPV[2].size())
    1321             fSP = fPV[0];
    1322 
    1323         if (!fPV[2].size())
    1324             fSP = fPV[1];
    1325         else
    1326             fSP = fPV[2];
    1327 
    1328         vector<float> vec(BIAS::kNumChannels);
    1329         for (int i=0; i<BIAS::kNumChannels; i++)
    1330             vec[i] = fSP[i];
    1331         fDimReference.Update(vec);
    1332 
    1333         return GetCurrentState();
    1334     }
    1335 
    1336     int SetReference(const EventImp &evt)
    1337     {
    1338         if (!CheckEventSize(evt.GetSize(), "SetReference", 4))
    1339             return kSM_FatalError;
    1340 
    1341         const float val = evt.GetFloat();
    1342         /*
    1343         if (!fPV[0].size() && !fPV[1].size() && !fPV[2].size())
    1344         {
    1345             Warn("No values in memory. Take enough events first!");
    1346             return GetCurrentState();
    1347         }*/
    1348 
    1349         vector<float> vec(BIAS::kNumChannels);
    1350         for (int i=0; i<BIAS::kNumChannels; i++)
    1351             vec[i] = fSP[i] = val;
    1352         fDimReference.Update(vec);
    1353 
    1354         Out() << "New global reference value: " << val << "mV" << endl;
    1355 
    1356         return GetCurrentState();
    1357     }
    1358 
    1359     int CalibrateCurrents()
    1360     {
    1361 //        if (!CheckEventSize(evt.GetSize(), "StartTempCtrl", 4))
    1362 //            return kSM_FatalError;
    1363 
    1364         if (fDimBias.state()==BIAS::State::kRamping)
    1365         {
    1366             Warn("Calibration cannot be started when biasctrl is in state Ramping.");
    1367             return GetCurrentState();
    1368         }
    1369 
    1370         if (fVoltGapd.empty())
    1371         {
    1372             Error("No G-APD reference voltages received yet (BIAS_CONTROL/NOMINAL).");
    1373             return GetCurrentState();
    1374         }
    1375 
    1376         WarnState(true, false);
    1377 
    1378         ostringstream out;
    1379         out << "Starting temperature feedback for calibration with an offset of " << fCalibrationOffset << "V";
    1380         Message(out);
    1381 
    1382         fBiasOffset = fCalibrationOffset;
    1383         fControlType = kTemp;
    1384         fCursorCur  = -fNumCalibIgnore;
    1385         fCursorTemp = 0;
    1386         fCurrentsAvg.assign(BIAS::kNumChannels, 0);
    1387         fCurrentsRms.assign(BIAS::kNumChannels, 0);
    1388         fCalibration.resize(0);
    1389         fStartTime = Time();
    1390         fOutputEnabled = true;
    1391 
    1392         return Feedback::State::kCalibrating;
    1393     }
    1394 
    1395     int SetCurrentRequestInterval(const EventImp &evt)
    1396     {
    1397         if (!CheckEventSize(evt.GetSize(), "SetCurrentRequestInterval", 2))
    1398             return kSM_FatalError;
    1399 
    1400         fCurrentRequestInterval = evt.GetUShort();
    1401 
    1402         Out() << "New current request interval: " << fCurrentRequestInterval << "ms" << endl;
    1403 
    1404         return GetCurrentState();
    1405     }
    1406 
    1407     int Execute()
    1408     {
    1409         // Dispatch (execute) at most one handler from the queue. In contrary
    1410         // to run_one(), it doesn't wait until a handler is available
    1411         // which can be dispatched, so poll_one() might return with 0
    1412         // handlers dispatched. The handlers are always dispatched/executed
    1413         // synchronously, i.e. within the call to poll_one()
    1414         //poll_one();
    1415 
    1416         if (!fDim.online())
    1417             return Feedback::State::kDimNetworkNA;
    1418 
    1419         const bool bias = fDimBias.state() >= BIAS::State::kConnecting;
    1420         const bool fad  = fDimFAD.state()  >= FAD::State::kConnected;
    1421         const bool fsc  = fDimFSC.state()  >= FSC::State::kConnected;
    1422 
    1423         // All subsystems are not connected
    1424         if (!bias && !fad && !fsc)
    1425             return Feedback::State::kDisconnected;
    1426 
    1427         // At least one subsystem apart from bias is connected
    1428         if (bias && !fad && !fsc)
    1429             return Feedback::State::kConnecting;
    1430 
    1431 /*
    1432         // All subsystems are connected
    1433         if (GetCurrentStatus()==Feedback::State::kConfiguringStep1)
    1434         {
    1435             if (fCursor<1)
    1436                 return Feedback::State::kConfiguringStep1;
    1437 
    1438             if (fCursor==1)
    1439             {
    1440                 fStartTime = Time();
    1441                 return Feedback::State::kConfiguringStep2;
    1442             }
    1443         }
    1444         if (GetCurrentStatus()==Feedback::State::kConfiguringStep2)
    1445         {
    1446             if (fCursor==1)
    1447             {
    1448                 if ((Time()-fStartTime).total_microseconds()/1000000.<1.5)
    1449                     return Feedback::State::kConfiguringStep2;
    1450 
    1451                 Dim::SendCommand("BIAS_CONTROL/REQUEST_STATUS");
    1452             }
    1453             if (fCursor==2)
    1454             {
    1455 
    1456                 int n=0;
    1457                 double avg = 0;
    1458                 for (size_t i=0; i<fCurrents.size(); i++)
    1459                     if (fCurrents[i]>=0)
    1460                     {
    1461                         avg += fCurrents[i];
    1462                         n++;
    1463                     }
    1464 
    1465                 cout << avg/n << endl;
    1466             }
    1467             return Feedback::State::kConnected;
    1468         }
    1469         */
    1470 
    1471         // Needs connection of FAD and BIAS
    1472         if (bias && fad)
    1473         {
    1474             if (fControlType==kFeedback || fControlType==kFeedbackGlobal)
    1475                 return fOutputEnabled ? Feedback::State::kFeedbackCtrlRunning : Feedback::State::kFeedbackCtrlIdle;
    1476         }
    1477 
    1478         // Needs connection of FSC and BIAS
    1479         if (bias && fsc)
    1480         {
    1481             if (fControlType==kTemp)
    1482             {
    1483                 if (GetCurrentState()==Feedback::State::kCalibrating && fCursorCur<fNumCalibRequests)
    1484                     return GetCurrentState();
    1485 
    1486                 return fOutputEnabled ? Feedback::State::kTempCtrlRunning : Feedback::State::kTempCtrlIdle;
    1487             }
    1488             if (fControlType==kCurrents || fControlType==kCurrentsNew)
    1489             {
    1490                 static Time past;
    1491                 if (fCurrentRequestInterval>0 && Time()-past>boost::posix_time::milliseconds(fCurrentRequestInterval))
    1492                 {
    1493                     if (fDimBias.state()==BIAS::State::kVoltageOn)
    1494                         DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
    1495                     past = Time();
    1496                 }
    1497 
    1498                 return fOutputEnabled && fCursorTemp>0 ? Feedback::State::kCurrentCtrlRunning : Feedback::State::kCurrentCtrlIdle;
    1499             }
    1500         }
    1501 
    1502         if (bias && fad && !fsc)
    1503             return Feedback::State::kConnectedFAD;
    1504 
    1505         if (bias && fsc && !fad)
    1506             return Feedback::State::kConnectedFSC;
    1507 
    1508         return Feedback::State::kConnected;
    1509881    }
    1510882
    1511883public:
    1512884    StateMachineFeedback(ostream &out=cout) : StateMachineDim(out, "FEEDBACK"),
     885        fIsVerbose(false), fEnableOldAlgorithm(true),
    1513886        //---
    1514         fDimFAD("FAD_CONTROL"),
    1515887        fDimFSC("FSC_CONTROL"),
    1516888        fDimBias("BIAS_CONTROL"),
    1517889        //---
    1518         fDimReference("FEEDBACK/REFERENCE", "F:416",
    1519                       "Amplitude reference value(s)"
    1520                       "Vref[mV]:Amplitude reference"),
    1521         fDimDeviation("FEEDBACK/DEVIATION", "F:416;F:416;F:1;F:1",
    1522                       "Control loop information"
    1523                       "|DeltaAmpl[mV]:Amplitude offset measures"
    1524                       "|DeltaBias[mV]:Correction value calculated"
    1525                       "|DeltaTemp[mV]:Correction calculated from temperature"
    1526                       "|DeltaUser[mV]:Additional offset specified by user"),
    1527         fDimCalibration("FEEDBACK/CALIBRATION", "F:416;F:416;F:416",
     890        fDimCalibration("FEEDBACK/CALIBRATION", "F:416;F:416;F:416;F:416",
    1528891                        "Current offsets"
    1529                         "|Avg[uA]:Average offset"
    1530                         "|Rms[uA]:Rms of offset"
    1531                         "|R[Ohm]:Measured calibration resistor"),
    1532         fDimCurrents("FEEDBACK/CALIBRATED_CURRENTS", "F:416;F:1;F:1;F:1;F:1;I:1;F:1",
     892                        "|Avg[uA]:Average offset at dac=256+5*512"
     893                        "|Rms[uA]:Rms of Avg"
     894                        "|R[Ohm]:Measured calibration resistor"
     895                        "|U[V]:Corresponding voltage reported by biasctrl"),
     896        fDimCalibration2("FEEDBACK/CALIBRATION_STEPS", "I:1;F:416;F:416;F:416",
     897                        "Calibration of the R8 resistor"
     898                        "|DAC[dac]:DAC setting"
     899                        "|U[V]:Corresponding voltages reported by biasctrl"
     900                        "|Iavg[uA]:Averaged measured current"
     901                        "|Irms[uA]:Rms measured current"),
     902        fDimCalibrationR8("FEEDBACK/CALIBRATION_R8", "F:416;F:416",
     903                          "Calibration of R8"
     904                          "|DeltaI[uA]:Average offset"
     905                          "|R8[uA]:Measured effective resistor R8"),
     906        fDimCurrents("FEEDBACK/CALIBRATED_CURRENTS", "F:416;F:1;F:1;F:1;F:1;I:1;F:1;F:416;F:1;F:1",
    1533907                     "Calibrated currents"
    1534                      "|I[uA]:Calibrated currents"
    1535                      "|I_avg[uA]:Average calibrated current (320 channels)"
    1536                      "|I_rms[uA]:Rms of calibrated current (320 channels)"
    1537                      "|I_med[uA]:Median calibrated current (320 channels)"
    1538                      "|I_dev[uA]:Deviation of calibrated current (320 channels)"
     908                     "|I[uA]:Calibrated currents per pixel"
     909                     "|I_avg[uA]:Average calibrated current (N channels)"
     910                     "|I_rms[uA]:Rms of calibrated current (N channels)"
     911                     "|I_med[uA]:Median calibrated current (N channels)"
     912                     "|I_dev[uA]:Deviation of calibrated current (N channels)"
    1539913                     "|N[uint16]:Number of valid values"
    1540                      "|T_diff[s]:Time difference to calibration"),
    1541         fSP(BIAS::kNumChannels),
    1542         fKp(0), fKi(0), fKd(0), fT(-1),
    1543         fCalibrationOffset(-3),
     914                     "|T_diff[s]:Time difference to calibration"
     915                     "|U_ov[V]:Calculated overvoltage"
     916                     "|U_nom[V]:Nominal overvoltage"
     917                     "|dU_temp[V]:Correction calculated from temperature"
     918                    ),
    1544919        fCurrentRequestInterval(0),
    1545920        fNumCalibIgnore(30),
    1546         fNumCalibRequests(300),
    1547         fOutputEnabled(false)
    1548     {
    1549         // ba::io_service::work is a kind of keep_alive for the loop.
    1550         // It prevents the io_service to go to stopped state, which
    1551         // would prevent any consecutive calls to run()
    1552         // or poll() to do nothing. reset() could also revoke to the
    1553         // previous state but this might introduce some overhead of
    1554         // deletion and creation of threads and more.
    1555 
     921        fNumCalibRequests(300)
     922    {
    1556923        fDim.Subscribe(*this);
    1557         fDimFAD.Subscribe(*this);
    1558924        fDimFSC.Subscribe(*this);
    1559925        fDimBias.Subscribe(*this);
     926
     927        fDimBias.SetCallback(bind(&StateMachineFeedback::HandleBiasStateChange, this));
    1560928
    1561929        Subscribe("BIAS_CONTROL/CURRENT")
     
    1563931        Subscribe("BIAS_CONTROL/VOLTAGE")
    1564932            (bind(&StateMachineFeedback::HandleBiasVoltage, this, placeholders::_1));
    1565         Subscribe("BIAS_CONTROL/FEEDBACK_DATA")
    1566             (bind(&StateMachineFeedback::HandleBiasData,    this, placeholders::_1));
     933        Subscribe("BIAS_CONTROL/DAC")
     934            (bind(&StateMachineFeedback::HandleBiasDac,     this, placeholders::_1));
    1567935        Subscribe("BIAS_CONTROL/NOMINAL")
    1568936            (bind(&StateMachineFeedback::HandleBiasNom,     this, placeholders::_1));
     
    1576944        AddStateName(Feedback::State::kDisconnected, "Disconnected",
    1577945                     "The Dim DNS is reachable, but the required subsystems are not available.");
    1578 
    1579946        AddStateName(Feedback::State::kConnecting, "Connecting",
    1580                      "Only biasctrl is available and connected with its hardware.");
    1581 
    1582         AddStateName(Feedback::State::kConnectedFSC, "ConnectedFSC",
     947                     "Either biasctrl or fscctrl not connected.");
     948        AddStateName(Feedback::State::kConnected, "Connected",
    1583949                     "biasctrl and fscctrl are available and connected with their hardware.");
    1584         AddStateName(Feedback::State::kConnectedFAD, "ConnectedFAD",
    1585                      "biasctrl and fadctrl are available and connected with their hardware.");
    1586         AddStateName(Feedback::State::kConnected, "Connected",
    1587                      "biasctrl, fadctrl and fscctrl are available and connected with their hardware.");
    1588 
    1589         AddStateName(Feedback::State::kFeedbackCtrlIdle, "FeedbackIdle",
    1590                      "Feedback control activated, but voltage output disabled.");
    1591         AddStateName(Feedback::State::kTempCtrlIdle, "TempCtrlIdle",
    1592                      "Temperature control activated, but voltage output disabled.");
    1593         AddStateName(Feedback::State::kCurrentCtrlIdle, "CurrentCtrlIdle",
    1594                      "Current control activated, but voltage output disabled.");
    1595 
    1596         AddStateName(Feedback::State::kFeedbackCtrlRunning, "FeedbackControl",
    1597                      "Feedback control activated and voltage output enabled.");
    1598         AddStateName(Feedback::State::kTempCtrlRunning, "TempControl",
    1599                      "Temperature control activated and voltage output enabled.");
    1600         AddStateName(Feedback::State::kCurrentCtrlRunning, "CurrentControl",
    1601                      "Current/Temp control activated and voltage output enabled.");
     950
    1602951        AddStateName(Feedback::State::kCalibrating, "Calibrating",
    1603                      "Calibrating current offsets.");
    1604 
    1605         AddEvent("START_FEEDBACK_CONTROL", "S:1", Feedback::State::kConnectedFAD, Feedback::State::kConnected)
    1606             (bind(&StateMachineFeedback::StartFeedback, this, placeholders::_1))
    1607             ("Start the feedback control loop"
    1608              "|Num[short]:Number of events 'medianed' to calculate the correction value");
    1609 
    1610         AddEvent("START_GLOBAL_FEEDBACK", "S:1", Feedback::State::kConnectedFAD, Feedback::State::kConnected)
    1611             (bind(&StateMachineFeedback::StartFeedbackGlobal, this, placeholders::_1))
    1612             ("Start the global feedback control loop"
    1613              "Num[short]:Number of events averaged to calculate the correction value");
    1614 
    1615         AddEvent("START_TEMP_CONTROL", "F:1", Feedback::State::kConnectedFSC, Feedback::State::kConnected)
    1616             (bind(&StateMachineFeedback::StartTempCtrl, this, placeholders::_1))
    1617             ("Start the temperature control loop"
    1618              "|offset[V]:Offset from the nominal temperature corrected value in Volts");
    1619 
    1620         AddEvent("START_CURRENT_CONTROL", "F:1", Feedback::State::kConnectedFSC, Feedback::State::kConnected)
    1621             (bind(&StateMachineFeedback::StartCurrentCtrl, this, placeholders::_1))
     952                     "Bias crate calibrating in progress.");
     953        AddStateName(Feedback::State::kCalibrated, "Calibrated",
     954                     "Bias crate calibrated.");
     955
     956        AddStateName(Feedback::State::kWaitingForData, "WaitingForData",
     957                     "Current control started, waiting for valid temperature and current data.");
     958        AddStateName(Feedback::State::kInProgress, "InProgress",
     959                     "Current control in progress.");
     960
     961
     962        /*
     963        AddEvent("SET_CURRENT_REQUEST_INTERVAL")
     964            (bind(&StateMachineFeedback::SetCurrentRequestInterval, this, placeholders::_1))
     965            ("|interval[ms]:Interval between two current requests in modes which need that.");
     966        */
     967
     968        AddEvent("CALIBRATE", Feedback::State::kConnected, Feedback::State::kCalibrated)
     969            (bind(&StateMachineFeedback::Calibrate, this))
     970            ("");
     971
     972        AddEvent("START", "F:1", Feedback::State::kCalibrated)
     973            (bind(&StateMachineFeedback::Start, this, placeholders::_1))
    1622974            ("Start the current/temperature control loop"
    1623              "|offset[V]:Offset from the nominal current/temperature corrected value in Volts");
    1624 
    1625         // Feedback::State::kTempCtrlIdle, Feedback::State::kFeedbackCtrlIdle, Feedback::State::kTempCtrlRunning, Feedback::State::kFeedbackCtrlRunning
     975             "|Uov[V]:Overvoltage to be applied (standard value is 1.1V)");
     976
    1626977        AddEvent("STOP")
    1627978            (bind(&StateMachineFeedback::StopFeedback, this))
    1628979            ("Stop any control loop");
    1629980
    1630         AddEvent("ENABLE_OUTPUT", "B:1")//, Feedback::State::kIdle)
    1631             (bind(&StateMachineFeedback::EnableOutput, this, placeholders::_1))
    1632             ("Enable sending of correction values caluclated by the control loop to the biasctrl");
    1633 
    1634         AddEvent("STORE_REFERENCE")//, Feedback::State::kIdle)
    1635             (bind(&StateMachineFeedback::StoreReference, this))
    1636             ("Store the last (averaged) value as new reference (for debug purpose only)");
    1637 
    1638         AddEvent("SET_REFERENCE", "F:1")//, Feedback::State::kIdle)
    1639             (bind(&StateMachineFeedback::SetReference, this, placeholders::_1))
    1640             ("Set a new global reference value (for debug purpose only)");
    1641 
    1642         AddEvent("SET_Ki", "D:1")//, Feedback::State::kIdle)
    1643             (bind(&StateMachineFeedback::SetConstant, this, placeholders::_1, 0))
    1644             ("Set integral constant Ki");
    1645 
    1646         AddEvent("SET_Kp", "D:1")//, Feedback::State::kIdle)
    1647             (bind(&StateMachineFeedback::SetConstant, this, placeholders::_1, 1))
    1648             ("Set proportional constant Kp");
    1649 
    1650         AddEvent("SET_Kd", "D:1")//, Feedback::State::kIdle)
    1651             (bind(&StateMachineFeedback::SetConstant, this, placeholders::_1, 2))
    1652             ("Set derivative constant Kd");
    1653 
    1654         AddEvent("SET_T", "D:1")//, Feedback::State::kIdle)
    1655             (bind(&StateMachineFeedback::SetConstant, this, placeholders::_1, 3))
    1656             ("Set time-constant. (-1 to use the cycle time, i.e. the time for the last average cycle, instead)");
    1657 
    1658         AddEvent("CALIBRATE_CURRENTS", Feedback::State::kConnectedFSC, Feedback::State::kConnected)//, Feedback::State::kIdle)
    1659             (bind(&StateMachineFeedback::CalibrateCurrents, this))
    1660             ("");
    1661 
    1662         AddEvent("SET_CURRENT_REQUEST_INTERVAL", Feedback::State::kConnectedFSC, Feedback::State::kConnected)//, Feedback::State::kIdle)
    1663             (bind(&StateMachineFeedback::SetCurrentRequestInterval, this, placeholders::_1))
    1664             ("|interval[ms]:Interval between two current requests in modes which need that.");
    1665 
    1666         // Verbosity commands
    1667 //        AddEvent("SET_VERBOSE", "B:1")
    1668 //            (bind(&StateMachineMCP::SetVerbosity, this, placeholders::_1))
    1669 //            ("set verbosity state"
    1670 //             "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
     981
     982        AddEvent("ENABLE_OLD_ALRGORITHM", "B:1", Feedback::State::kConnected, Feedback::State::kCalibrated)
     983            (bind(&StateMachineFeedback::EnableOldAlgorithm, this, placeholders::_1));
     984
    1671985
    1672986        AddEvent("PRINT")
    1673987            (bind(&StateMachineFeedback::Print, this))
    1674988            ("");
    1675 
    1676989        AddEvent("PRINT_CALIBRATION")
    1677990            (bind(&StateMachineFeedback::PrintCalibration, this))
    1678991            ("");
     992
     993        // Verbosity commands
     994        AddEvent("SET_VERBOSE", "B:1")
     995            (bind(&StateMachineFeedback::SetVerbosity, this, placeholders::_1))
     996            ("set verbosity state"
     997             "|verbosity[bool]:disable or enable verbosity when calculating overvoltage");
    1679998    }
    1680999
    16811000    int EvalOptions(Configuration &conf)
    16821001    {
     1002        fIsVerbose = !conf.Get<bool>("quiet");
     1003
    16831004        if (!fMap.Read(conf.Get<string>("pixel-map-file")))
    16841005        {
     
    16861007            return 1;
    16871008        }
    1688 
    1689         fGain = 0.1; // V(Amplitude) / V(Bias)
    1690 
    1691         // 148 -> 248
    1692 
    1693         // 33 : 10s  < 2%
    1694         // 50 :  5s  < 2%
    1695         // 66 :  3s  < 2%
    1696         // 85 :  2s  < 2%
    1697 
    1698         fKp = 0;
    1699         fKd = 0;
    1700         fKi = 0.75;
    1701         fT  = 1;
    1702 
    1703         // Is that independent of the aboslute real amplitude of
    1704         // the light pulser?
    1705 
    1706         ostringstream msg;
    1707         msg << "Control loop parameters: ";
    1708         msg << "Kp=" << fKp << ", Kd=" << fKd << ", Ki=" << fKi << ", ";
    1709         if (fT>0)
    1710             msg << fT;
    1711         else
    1712             msg << "<auto>";
    1713         msg << ", Gain(DRS/BIAS)=" << fGain << "V/V";
    1714 
    1715         Message(msg);
    17161009
    17171010        fCurrentRequestInterval = conf.Get<uint16_t>("current-request-interval");
    17181011        fNumCalibIgnore         = conf.Get<uint16_t>("num-calib-ignore");
    17191012        fNumCalibRequests       = conf.Get<uint16_t>("num-calib-average");
    1720         fCalibrationOffset      = conf.Get<float>("calibration-offset");
    17211013
    17221014        return -1;
     
    17381030    po::options_description control("Feedback options");
    17391031    control.add_options()
     1032        ("quiet,q", po_bool(true), "Disable printing more information on average overvoltagecontents of all received messages (except dynamic data) in clear text.")
    17401033        ("pixel-map-file",      var<string>()->required(), "Pixel mapping file. Used here to get the default reference voltage.")
    17411034        ("current-request-interval",  var<uint16_t>(1000), "Interval between two current requests.")
    17421035        ("num-calib-ignore",    var<uint16_t>(30), "Number of current requests to be ignored before averaging")
    17431036        ("num-calib-average",   var<uint16_t>(300), "Number of current requests to be averaged")
    1744         ("calibration-offset",  var<float>(-3), "Absolute offset relative to the G-APD operation voltage when calibrating")
    17451037        ;
    17461038
Note: See TracChangeset for help on using the changeset viewer.