source: trunk/FACT++/src/feedback.cc@ 13251

Last change on this file since 13251 was 13249, checked in by tbretz, 13 years ago
Fixed the 'broken pixel' correction. The voltage was devided by the correction instead of multiplied.
File size: 42.7 KB
Line 
1#include <valarray>
2
3#include "Dim.h"
4#include "Event.h"
5#include "Shell.h"
6#include "StateMachineDim.h"
7#include "Connection.h"
8#include "Configuration.h"
9#include "Console.h"
10#include "Converter.h"
11#include "DimServiceInfoList.h"
12#include "PixelMap.h"
13
14#include "tools.h"
15
16#include "LocalControl.h"
17
18#include "HeadersFAD.h"
19#include "HeadersBIAS.h"
20
21namespace ba = boost::asio;
22namespace bs = boost::system;
23namespace dummy = ba::placeholders;
24
25using namespace std;
26
27// ------------------------------------------------------------------------
28
29#include "DimDescriptionService.h"
30
31// ------------------------------------------------------------------------
32
33class StateMachineFeedback : public StateMachineDim, public DimInfoHandler
34{
35 /*
36 int Wrap(boost::function<void()> f)
37 {
38 f();
39 return T::GetCurrentState();
40 }
41
42 boost::function<int(const EventImp &)> Wrapper(boost::function<void()> func)
43 {
44 return bind(&StateMachineMCP::Wrap, this, func);
45 }*/
46
47private:
48 enum states_t
49 {
50 kStateDimNetworkNA = 1,
51 kStateDisconnected,
52 kStateConnecting,
53 kStateConnectedFSC,
54 kStateConnectedFAD,
55 kStateConnected,
56 kStateTempCtrlIdle, // 7
57 kStateFeedbackCtrlIdle, // 8
58 kStateCurrentCtrlIdle, // 9
59 kStateTempCtrlRunning, // 9->10
60 kStateFeedbackCtrlRunning, // 10->11
61 kStateCurrentCtrlRunning, // 12
62 kStateCalibrating, // 11->13
63 };
64
65 enum control_t
66 {
67 kIdle,
68 kTemp,
69 kFeedback,
70 kFeedbackGlobal,
71 kCurrents,
72 };
73
74 control_t fControlType;
75
76 PixelMap fMap;
77
78 DimServiceInfoList fNetwork;
79
80 pair<Time, int> fStatusDim;
81 pair<Time, int> fStatusFAD;
82 pair<Time, int> fStatusFSC;
83 pair<Time, int> fStatusBias;
84
85 DimStampedInfo fDim;
86 DimStampedInfo fFAD;
87 DimStampedInfo fFSC;
88 DimStampedInfo fBias;
89 DimStampedInfo fBiasA;
90
91 DimStampedInfo fBiasData;
92 DimStampedInfo fCameraTemp;
93
94 DimDescribedService fDimReference;
95 DimDescribedService fDimDeviation;
96 DimDescribedService fDimCalibration;
97
98 vector<int64_t> fCurrentsAvg;
99 vector<int64_t> fCurrentsRms;
100
101 vector<float> fCalibration;
102
103 vector<vector<float>> fData;
104
105 int64_t fCursorCur;
106 uint64_t fCursorAmpl;
107 uint64_t fCursorTemp;
108
109 Time fBiasLast;
110 Time fStartTime;
111
112 valarray<double> fPV[3]; // Process variable (intgerated/averaged amplitudes)
113 valarray<double> fSP; // Set point (target amplitudes)
114
115 double fKp; // Proportional constant
116 double fKi; // Integral constant
117 double fKd; // Derivative constant
118 double fT; // Time constant (cycle time)
119 double fGain; // Gain (conversion from a DRS voltage deviation into a BIAS voltage change at G-APD reference voltage)
120
121 double fT21;
122
123 double fBiasOffset;
124
125 uint16_t fCurrentRequestInterval;
126 uint16_t fNumCalibIgnore;
127 uint16_t fNumCalibRequests;
128
129 bool fOutputEnabled;
130
131 pair<Time, int> GetNewState(DimStampedInfo &info) const
132 {
133 const bool disconnected = info.getSize()==0;
134
135 // Make sure getTimestamp is called _before_ getTimestampMillisecs
136 const int tsec = info.getTimestamp();
137 const int tms = info.getTimestampMillisecs();
138
139 return make_pair(Time(tsec, tms*1000),
140 disconnected ? -2 : info.getQuality());
141 }
142
143 void HandleCameraTemp()
144 {
145 if (fCameraTemp.getSize()!=60*sizeof(float))
146 return;
147
148 const float *ptr = static_cast<float*>(fCameraTemp.getData());
149
150 double avgt = 0;
151 int numt = 0;
152 for (int i=1; i<32; i++)
153 if (ptr[i]!=0)
154 {
155 avgt += ptr[i];
156 numt++;
157 }
158
159 if (numt==0)
160 return;
161
162 avgt /= numt;
163
164
165 const float diff = (avgt-25)*4./70 + fBiasOffset;
166
167 vector<float> vec(2*BIAS::kNumChannels);
168 for (int i=0; i<BIAS::kNumChannels; i++)
169 vec[i+BIAS::kNumChannels] = diff;
170
171 double avg[2] = { 0, 0 };
172 double min[2] = { 90, 90 };
173 double max[2] = { -90, -90 };
174 int num[2] = { 0, 0 };
175
176 vector<double> med[2];
177 med[0].resize(416);
178 med[1].resize(416);
179
180 if (fControlType==kCurrents)
181 {
182 if (fCursorCur==0)
183 {
184 //DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
185 return;
186 }
187
188 // Pixel 583: 5 31 == 191 (5)
189 // Pixel 830: 2 2 == 66 (4)
190 // Pixel 1401: 6 1 == 193 (4)
191
192 // Convert from DAC counts to uA
193 const double conv = 5000e-6/4096;
194
195 // 3900 Ohm/n + 1000 Ohm + 1100 Ohm (with n=4 or n=5)
196 const double R[2] = { 3075, 2870 };
197
198 for (int i=0; i<BIAS::kNumChannels; i++)
199 {
200 const PixelMapEntry &hv = fMap.hv(i);
201 if (!hv)
202 continue;
203
204 const int g = hv.group();
205
206 const double Im = double(fCurrentsAvg[i])/fCursorCur;
207 const double I = Im>fCalibration[i] ? Im-fCalibration[i] : 0;
208
209 double U = R[g] * I*conv;
210
211 // 510 / 390 1.30 ^1.66 = 1.55
212 // 470 / 380 1.23 = 1.41
213 // 450 / 360 1.25 = 1.45
214
215 // This is assuming that the broken pixels
216 // have a 1kOhm instead of 390 Ohm serial resistor
217 if (i==66 || i==193)
218 U *= 2100./R[0]; // (1k)2665 / (390)2400 / (~0)2100
219 if (i==191)
220 U *= 2100./R[1]; // (1k)2794 / (390)2320 / (~0)2100
221
222 vec[i+BIAS::kNumChannels] += U;
223
224 if (fCalibration[i]>0)
225 {
226 med[g][num[g]] = U;
227 avg[g] += U;
228 num[g]++;
229
230 if (U<min[g])
231 min[g] = U;
232 if (U>max[g])
233 max[g] = U;
234 }
235 }
236
237 sort(med[0].begin(), med[0].begin()+num[0]);
238 sort(med[1].begin(), med[1].begin()+num[1]);
239
240 fCurrentsAvg.assign(BIAS::kNumChannels, 0);
241 fCursorCur = 0;
242 }
243
244 fDimDeviation.setQuality(fControlType);
245 fDimDeviation.Update(vec);
246
247 if (!fOutputEnabled || fStatusBias.second!=BIAS::kVoltageOn)
248 return;
249
250 // Trigger calibration
251 if (GetCurrentState()==kStateCalibrating && fCursorTemp==1)
252 {
253 DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
254 return;
255 }
256
257 ostringstream msg;
258 msg << setprecision(4) << "Sending new absolute offset (" << diff << "V+" << (num[0]+num[1]>0?(avg[0]+avg[1])/(num[0]+num[1]):0) << "V) to biasctrl.";
259 Info(msg);
260
261 if (fControlType==kCurrents && num[0]>0 && num[1]>0)
262 {
263 msg.str("");
264 msg << " Avg0=" << setw(7) << avg[0]/num[0] << " | Avg1=" << setw(7) << avg[1]/num[1];
265 Debug(msg);
266
267 msg.str("");
268 msg << " Med0=" << setw(7) << med[0][num[0]/2] << " | Med1=" << setw(7) << med[1][num[1]/2];
269 Debug(msg);
270
271 msg.str("");
272 msg << " Min0=" << setw(7) << min[0] << " | Min1=" << setw(7) << min[1];
273 Debug(msg);
274
275 msg.str("");
276 msg << " Max0=" << setw(7) << max[0] << " | Max1=" << setw(7) << max[1];
277 Debug(msg);
278 }
279
280 DimClient::sendCommandNB("BIAS_CONTROL/SET_ALL_CHANNELS_OFFSET",
281 vec.data()+BIAS::kNumChannels, BIAS::kNumChannels*sizeof(float));
282
283 fCursorTemp++;
284 }
285
286 int AverageCurrents()
287 {
288 if (fBiasA.getSize()!=BIAS::kNumChannels*sizeof(int16_t))
289 return -1;
290
291 if (fStatusBias.second!=BIAS::kVoltageOn)
292 return false;
293
294 if (fCursorCur++<0)
295 return true;
296
297 const int16_t *ptr = static_cast<int16_t*>(fBiasA.getData());
298
299 for (int i=0; i<BIAS::kNumChannels; i++)
300 {
301 fCurrentsAvg[i] += ptr[i];
302 fCurrentsRms[i] += ptr[i]*ptr[i];
303 }
304
305 return true;
306 }
307
308
309 void HandleCalibration()
310 {
311 const int rc = AverageCurrents();
312 if (rc<0)
313 return;
314
315 if (fCursorCur<fNumCalibRequests)
316 {
317 if (fStatusBias.second==BIAS::kVoltageOn)
318 DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
319 return;
320 }
321
322 if (rc==0)
323 return;
324
325 fCalibration.resize(BIAS::kNumChannels*2);
326 for (int i=0; i<BIAS::kNumChannels; i++)
327 {
328 fCalibration[i] = double(fCurrentsAvg[i])/fCursorCur;
329 fCalibration[i+BIAS::kNumChannels] = sqrt(double(fCurrentsRms[i])/fCursorCur-fCalibration[i]*fCalibration[i]);
330 }
331
332 fDimCalibration.Update(fCalibration);
333
334 fOutputEnabled = false;
335 fControlType = kIdle;
336
337 Info("Calibration successfully done.");
338
339 if (fStatusBias.second==BIAS::kVoltageOn)
340 DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
341 }
342
343 void HandleFeedback()
344 {
345 if (fBiasData.getSize()!=1440*sizeof(float))
346 return;
347
348 // -------- Check age of last stored event --------
349
350 // Must be called in this order
351 const int tsec = fBiasData.getTimestamp();
352 const int tms = fBiasData.getTimestampMillisecs();
353
354 const Time tm(tsec, tms*1000);
355
356 if (Time()-fBiasLast>boost::posix_time::seconds(30))
357 {
358 Warn("Last received event data older than 30s... resetting average calculation.");
359 ResetData();
360 }
361 fBiasLast = tm;
362
363 // -------- Store new event --------
364
365 fData[fCursorAmpl%fData.size()].assign(reinterpret_cast<float*>(fBiasData.getData()),
366 reinterpret_cast<float*>(fBiasData.getData())+1440);
367
368 if (++fCursorAmpl<fData.size())
369 return;
370
371 // -------- Calculate statistics --------
372
373 valarray<double> med(1440);
374
375 for (int ch=0; ch<1440; ch++)
376 {
377 vector<float> arr(fData.size());
378 for (size_t i=0; i<fData.size(); i++)
379 arr[i] = fData[i][ch];
380
381 sort(arr.begin(), arr.end());
382
383 med[ch] = arr[arr.size()/2];
384 }
385
386 /*
387 vector<float> med(1440);
388 vector<float> rms(1440);
389 for (size_t i=0; i<fData.size(); i++)
390 {
391 if (fData[i].size()==0)
392 return;
393
394 for (int j=0; j<1440; j++)
395 {
396 med[j] += fData[i][j];
397 rms[j] += fData[i][j]*fData[i][j];
398 }
399 }
400 */
401
402 vector<double> avg(BIAS::kNumChannels);
403 vector<int> num(BIAS::kNumChannels);
404 for (int i=0; i<1440; i++)
405 {
406 const PixelMapEntry &ch = fMap.hw(i);
407
408 // FIXME: Add a consistency check if the median makes sense...
409 // FIXME: Add a consistency check to remove pixels with bright stars (median?)
410
411 avg[ch.hv()] += med[i];
412 num[ch.hv()]++;
413 }
414
415 for (int i=0; i<BIAS::kNumChannels; i++)
416 {
417 if (num[i])
418 avg[i] /= num[i];
419
420 }
421
422 // -------- Calculate correction --------
423
424 // http://bestune.50megs.com/typeABC.htm
425
426 // CO: Controller output
427 // PV: Process variable
428 // SP: Set point
429 // T: Sampling period (loop update period)
430 // e = SP - PV
431 //
432 // Kp : No units
433 // Ki : per seconds
434 // Kd : seconds
435
436 // 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) ]
437
438 if (fCursorAmpl%fData.size()>0)
439 return;
440
441 // FIXME: Take out broken / dead boards.
442
443 const Time tm0 = Time();
444
445 /*const*/ double T21 = fT>0 ? fT : (tm0-fStartTime).total_microseconds()/1000000.;
446 const double T10 = fT21;
447 fT21 = T21;
448
449 fStartTime = tm0;
450
451 ostringstream out;
452 out << "New " << fData.size() << " event received: " << fCursorAmpl << " / " << setprecision(3) << T21 << "s";
453 Info(out);
454
455 if (fPV[0].size()==0)
456 {
457 fPV[0].resize(avg.size());
458 fPV[0] = valarray<double>(avg.data(), avg.size());
459 return;
460 }
461
462 if (fPV[1].size()==0)
463 {
464 fPV[1].resize(avg.size());
465 fPV[1] = valarray<double>(avg.data(), avg.size());
466 return;
467 }
468
469 if (fPV[2].size()==0)
470 {
471 fPV[2].resize(avg.size());
472 fPV[2] = valarray<double>(avg.data(), avg.size());
473 return;
474 }
475
476 fPV[0] = fPV[1];
477 fPV[1] = fPV[2];
478
479 fPV[2].resize(avg.size());
480 fPV[2] = valarray<double>(avg.data(), avg.size());
481
482 if (T10<=0 || T21<=0)
483 return;
484
485 //cout << "Calculating (" << fCursor << ":" << T21 << ")... " << endl;
486
487 // fKi[j] = response[j]*gain;
488 // Kp = 0;
489 // Kd = 0;
490
491 // => Kp = 0.01 * gain = 0.00005
492 // => Ki = 0.8 * gain/20s = 0.00025
493 // => Kd = 0.1 * gain/20s = 0.00003
494
495 /*
496 fKp = 0;
497 fKd = 0;
498 fKi = 0.00003*20;
499 T21 = 1;
500 */
501
502 //valarray<double> correction = - Kp*(PV[2] - PV[1]) + Ki * dT * (SP-PV[2]) - Kd/dT * (PV[2] - 2*PV[1] + PV[0]);
503 //valarray<double> correction =
504 // - Kp * (PV[2] - PV[1])
505 // + dT * Ki * (SP - PV[2])
506 // - Kd / dT * (PV[2] - 2*PV[1] + PV[0]);
507 //
508 // - (Kp+Kd/dT1) * (PV[2] - PV[1])
509 // + dT2 * Ki * (SP - PV[2])
510 // + Kd / dT1 * (PV[1] - PV[0]);
511 //
512 // - Kp * (PV[2] - PV[1])
513 // + Ki * (SP - PV[2])*dT
514 // - Kd * (PV[2] - PV[1])/dT
515 // + Kd * (PV[1] - PV[0])/dT;
516 //
517 //valarray<double> correction =
518 // - Kp*(PV[2] - PV[1]) + Ki * T21 * (SP-PV[2]) - Kd*(PV[2]-PV[1])/T21 - Kd*(PV[0]-PV[1])/T01;
519 const valarray<double> correction = 1./fGain/1000*
520 (
521 - (fKp+fKd/T21)*(fPV[2] - fPV[1])
522 + fKi*T21*(fSP-fPV[2])
523 + fKd/T10*(fPV[1]-fPV[0])
524 );
525
526 /*
527 integral = 0
528 start:
529 integral += (fSP - fPV[2])*dt
530
531 output = Kp*(fSP - fPV[2]) + Ki*integral - Kd*(fPV[2] - fPV[1])/dt
532
533 wait(dt)
534
535 goto start
536 */
537
538 vector<float> vec(2*BIAS::kNumChannels);
539 for (int i=0; i<BIAS::kNumChannels; i++)
540 vec[i] = fPV[2][i]-fSP[i];
541
542 for (int i=0; i<BIAS::kNumChannels; i++)
543 vec[i+BIAS::kNumChannels] = avg[i]<5*2.5 ? 0 : correction[i];
544
545 fDimDeviation.setQuality(fControlType);
546 fDimDeviation.Update(vec);
547
548 if (!fOutputEnabled || fStatusBias.second!=BIAS::kVoltageOn)
549 return;
550
551 Info("Sending new relative offset to biasctrl.");
552
553 DimClient::sendCommandNB("BIAS_CONTROL/INCREASE_ALL_CHANNELS_VOLTAGE",
554 vec.data()+BIAS::kNumChannels, BIAS::kNumChannels*sizeof(float));
555 }
556
557 void HandleGlobalFeedback()
558 {
559 if (fBiasData.getSize()!=1440*sizeof(float))
560 return;
561
562 // -------- Store new event --------
563
564 vector<float> arr(reinterpret_cast<float*>(fBiasData.getData()),
565 reinterpret_cast<float*>(fBiasData.getData())+1440);
566
567 sort(arr.begin(), arr.end());
568
569 const float med = arr[arr.size()/2];
570
571 fData[fCursorAmpl%fData.size()].resize(1); //assign(&med, &med);
572 fData[fCursorAmpl%fData.size()][0] = med; //assign(&med, &med);
573
574 if (++fCursorAmpl<fData.size())
575 return;
576
577 // -------- Calculate statistics --------
578
579 double avg=0;
580 double rms=0;
581 for (size_t i=0; i<fData.size(); i++)
582 {
583 avg += fData[i][0];
584 rms += fData[i][0]*fData[i][0];
585 }
586
587 avg /= fData.size();
588 rms /= fData.size();
589
590 rms = sqrt(rms-avg*avg);
591
592 // -------- Calculate correction --------
593
594 if (fCursorAmpl%fData.size()!=0)
595 return;
596
597 Out() << "Amplitude: " << avg << " +- " << rms << endl;
598
599 // FIXME: Take out broken / dead boards.
600
601 /*
602 ostringstream out;
603 out << "New " << fData.size() << " event received: " << fCursor << " / " << setprecision(3) << T21 << "s";
604 Info(out);
605 */
606
607 if (fPV[0].size()==0)
608 {
609 fPV[0].resize(1);
610 fPV[0] = valarray<double>(&avg, 1);
611 return;
612 }
613
614 if (fPV[1].size()==0)
615 {
616 fPV[1].resize(1);
617 fPV[1] = valarray<double>(&avg, 1);
618 return;
619 }
620
621 if (fPV[2].size()==0)
622 {
623 fPV[2].resize(1);
624 fPV[2] = valarray<double>(&avg, 1);
625 return;
626 }
627
628 fPV[0] = fPV[1];
629 fPV[1] = fPV[2];
630
631 fPV[2].resize(1);
632 fPV[2] = valarray<double>(&avg, 1);
633
634 // ----- Calculate average currents -----
635
636 vector<float> A(BIAS::kNumChannels);
637 for (int i=0; i<BIAS::kNumChannels; i++)
638 A[i] = double(fCurrentsAvg[i]) / fCursorCur;
639
640 fCurrentsAvg.assign(BIAS::kNumChannels, 0);
641 fCursorCur = 0;
642
643 // -------- Calculate correction --------
644
645 // correction = (fSP[0]-fPV[2])*fKi
646 /*
647 const double T21 = 1; // feedback is 1s
648 const double T10 = 1; // feedback is 20s
649
650 const valarray<double> correction = 1./fGain/1000*
651 (
652 - (fKp+fKd/T21)*(fPV[2] - fPV[1])
653 + fKi*T21*(fSP[0]-fPV[2])
654 + fKd/T10*(fPV[1]-fPV[0])
655 );
656 */
657
658 // pow of 1.6 comes from the non-linearity of the
659 // amplitude vs bias voltage
660 const valarray<double> correction = 1./fGain/1000*
661 (
662 //fKi*(pow(fSP[0], 1./1.6)-pow(fPV[2], 1./1.6))
663 fKi*(fSP[0]-fPV[2])
664 );
665
666 Out() << "Correction: " << correction[0] << "V (" << fSP[0] << ")" << endl;
667
668 const int nch = BIAS::kNumChannels;
669
670 // FIXME: Sanity check!
671
672 vector<float> vec;
673 vec.reserve(2*nch);
674 vec.insert(vec.begin(), nch, fPV[2][0]-fSP[0]);
675 vec.insert(vec.begin()+nch, nch, correction[0]);
676
677 fDimDeviation.setQuality(fControlType);
678 fDimDeviation.Update(vec);
679
680 if (!fOutputEnabled || fStatusBias.second!=BIAS::kVoltageOn)
681 return;
682
683 Info("Sending new global relative offset to biasctrl.");
684
685 DimClient::sendCommandNB("BIAS_CONTROL/INCREASE_ALL_CHANNELS_VOLTAGE",
686 vec.data()+BIAS::kNumChannels, BIAS::kNumChannels*sizeof(float));
687 }
688
689 void infoHandler()
690 {
691 DimInfo *curr = getInfo(); // get current DimInfo address
692 if (!curr)
693 return;
694
695 if (curr==&fBias)
696 {
697 fStatusBias = GetNewState(fBias);
698 return;
699 }
700
701 if (curr==&fFAD)
702 {
703 fStatusFAD = GetNewState(fFAD);
704 return;
705 }
706
707 if (curr==&fFSC)
708 {
709 fStatusFSC = GetNewState(fFSC);
710 return;
711 }
712
713 if (curr==&fDim)
714 {
715 fStatusDim = GetNewState(fDim);
716 fStatusDim.second = curr->getSize()==4 ? curr->getInt() : 0;
717 return;
718 }
719
720 if (curr==&fCameraTemp && (fControlType==kTemp || fControlType==kCurrents))
721 HandleCameraTemp();
722
723 if (curr==&fBiasA && fControlType==kTemp && GetCurrentState()==kStateCalibrating)
724 HandleCalibration();
725
726 if (curr==&fBiasA && (fControlType==kFeedbackGlobal || fControlType==kCurrents))
727 AverageCurrents();
728
729 if (curr==&fBiasData && fControlType==kFeedback)
730 HandleFeedback();
731
732 if (curr==&fBiasData && fControlType==kFeedbackGlobal)
733 HandleGlobalFeedback();
734 }
735
736 bool CheckEventSize(size_t has, const char *name, size_t size)
737 {
738 if (has==size)
739 return true;
740
741 ostringstream msg;
742 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
743 Fatal(msg);
744 return false;
745 }
746
747 void PrintState(const pair<Time,int> &state, const char *server)
748 {
749 const State rc = fNetwork.GetState(server, state.second);
750
751 Out() << state.first.GetAsStr("%H:%M:%S.%f").substr(0, 12) << " - ";
752 Out() << kBold << server << ": ";
753 Out() << rc.name << "[" << rc.index << "]";
754 Out() << kReset << " - " << kBlue << rc.comment << endl;
755 }
756
757 int Print()
758 {
759 Out() << fStatusDim.first.GetAsStr("%H:%M:%S.%f").substr(0, 12) << " - ";
760 Out() << kBold << "DIM_DNS: ";
761 if (fStatusDim.second==0)
762 Out() << "Offline" << endl;
763 else
764 Out() << "V" << fStatusDim.second/100 << 'r' << fStatusDim.second%100 << endl;
765
766 PrintState(fStatusFAD, "FAD_CONTROL");
767 PrintState(fStatusFSC, "FSC_CONTROL");
768 PrintState(fStatusBias, "BIAS_CONTROL");
769
770 return GetCurrentState();
771 }
772
773 int PrintCalibration()
774 {
775 if (fCalibration.size()==0)
776 {
777 Out() << "No calibration performed so far." << endl;
778 return GetCurrentState();
779 }
780
781 for (int k=0; k<13; k++)
782 for (int j=0; j<8; j++)
783 {
784 Out() << setw(2) << k << "|" << setw(2) << j*4 << "|";
785 for (int i=0; i<4; i++)
786 Out() << Tools::Form(" %6.1f+-%4.1f", fCalibration[k*32+j*4+i], fCalibration[k*32+j*4+i+BIAS::kNumChannels]);
787 Out() << endl;
788 }
789
790 return GetCurrentState();
791 }
792
793 int SetConstant(const EventImp &evt, int constant)
794 {
795 if (!CheckEventSize(evt.GetSize(), "SetConstant", 8))
796 return kSM_FatalError;
797
798 switch (constant)
799 {
800 case 0: fKi = evt.GetDouble(); break;
801 case 1: fKp = evt.GetDouble(); break;
802 case 2: fKd = evt.GetDouble(); break;
803 case 3: fT = evt.GetDouble(); break;
804 case 4: fGain = evt.GetDouble(); break;
805 default:
806 Fatal("SetConstant got an unexpected constant id -- this is a program bug!");
807 return kSM_FatalError;
808 }
809
810 return GetCurrentState();
811 }
812
813 int EnableOutput(const EventImp &evt)
814 {
815 if (!CheckEventSize(evt.GetSize(), "EnableOutput", 1))
816 return kSM_FatalError;
817
818 fOutputEnabled = evt.GetBool();
819
820 return GetCurrentState();
821 }
822
823 void ResetData(int16_t n=-1)
824 {
825 fData.assign(n>0 ? n : fData.size(), vector<float>(0));
826
827 fCursorAmpl = 0;
828 fCursorCur = 0;
829 fCursorTemp = 0;
830
831 fStartTime = Time();
832
833 fSP = valarray<double>(0., BIAS::kNumChannels);
834
835 vector<float> vec(2*BIAS::kNumChannels);
836 fDimDeviation.setQuality(kIdle);
837 fDimDeviation.Update(vec);
838
839 fPV[0].resize(0);
840 fPV[1].resize(0);
841 fPV[2].resize(0);
842
843 fCurrentsAvg.assign(BIAS::kNumChannels, 0);
844 fCurrentsRms.assign(BIAS::kNumChannels, 0);
845
846 if (fKp==0 && fKi==0 && fKd==0)
847 Warn("Control loop parameters are all set to zero.");
848 }
849
850 int StartFeedback(const EventImp &evt)
851 {
852 if (!CheckEventSize(evt.GetSize(), "StartFeedback", 2))
853 return kSM_FatalError;
854
855 ResetData(evt.GetShort());
856
857 fControlType = kFeedback;
858
859 return GetCurrentState();
860 }
861
862 int StartFeedbackGlobal(const EventImp &evt)
863 {
864 if (!CheckEventSize(evt.GetSize(), "StartFeedbackGlobal", 2))
865 return kSM_FatalError;
866
867 ResetData(evt.GetShort());
868
869 fControlType = kFeedbackGlobal;
870
871 return GetCurrentState();
872 }
873
874 int StartTempCtrl(const EventImp &evt)
875 {
876 if (!CheckEventSize(evt.GetSize(), "StartTempCtrl", 4))
877 return kSM_FatalError;
878
879 fBiasOffset = evt.GetFloat();
880 fControlType = kTemp;
881
882 ostringstream out;
883 out << "Starting temperature feedback with an offset of " << fBiasOffset << "V";
884 Message(out);
885
886 if (fStatusBias.second==BIAS::kVoltageOn)
887 DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
888
889 return GetCurrentState();
890 }
891
892 int StartCurrentCtrl(const EventImp &evt)
893 {
894 if (!CheckEventSize(evt.GetSize(), "StartCurrentCtrl", 4))
895 return kSM_FatalError;
896
897 if (fCalibration.size()==0)
898 {
899 Warn("Current control needs a bias crate calibration first... command ignored.");
900 return GetCurrentState();
901 }
902
903 ResetData(0);
904
905 fBiasOffset = evt.GetFloat();
906 fControlType = kCurrents;
907
908 ostringstream out;
909 out << "Starting current/temp feedback with an offset of " << fBiasOffset << "V";
910 Message(out);
911
912 return GetCurrentState();
913 }
914
915 int StopFeedback()
916 {
917 fControlType = kIdle;
918
919 return GetCurrentState();
920 }
921
922 int StoreReference()
923 {
924 if (!fPV[0].size() && !fPV[1].size() && !fPV[2].size())
925 {
926 Warn("No values in memory. Take enough events first!");
927 return GetCurrentState();
928 }
929
930 // FIXME: Check age
931
932 if (!fPV[1].size() && !fPV[2].size())
933 fSP = fPV[0];
934
935 if (!fPV[2].size())
936 fSP = fPV[1];
937 else
938 fSP = fPV[2];
939
940 vector<float> vec(BIAS::kNumChannels);
941 for (int i=0; i<BIAS::kNumChannels; i++)
942 vec[i] = fSP[i];
943 fDimReference.Update(vec);
944
945 return GetCurrentState();
946 }
947
948 int SetReference(const EventImp &evt)
949 {
950 if (!CheckEventSize(evt.GetSize(), "SetReference", 4))
951 return kSM_FatalError;
952
953 const float val = evt.GetFloat();
954 /*
955 if (!fPV[0].size() && !fPV[1].size() && !fPV[2].size())
956 {
957 Warn("No values in memory. Take enough events first!");
958 return GetCurrentState();
959 }*/
960
961 vector<float> vec(BIAS::kNumChannels);
962 for (int i=0; i<BIAS::kNumChannels; i++)
963 vec[i] = fSP[i] = val;
964 fDimReference.Update(vec);
965
966 Out() << "New global reference value: " << val << "mV" << endl;
967
968 return GetCurrentState();
969 }
970
971 int CalibrateCurrents()
972 {
973// if (!CheckEventSize(evt.GetSize(), "StartTempCtrl", 4))
974// return kSM_FatalError;
975
976 if (fStatusBias.second==BIAS::kRamping)
977 {
978 Warn("Calibration cannot be started when biasctrl is in state Ramping.");
979 return GetCurrentState();
980 }
981
982 ostringstream out;
983 out << "Starting temperature feedback for calibration with an offset of -2V";
984 Message(out);
985
986 fBiasOffset = -2;
987 fControlType = kTemp;
988 fCursorCur = -fNumCalibIgnore;
989 fCursorTemp = 0;
990 fCurrentsAvg.assign(BIAS::kNumChannels, 0);
991 fCurrentsRms.assign(BIAS::kNumChannels, 0);
992 fCalibration.resize(0);
993 fStartTime = Time();
994 fOutputEnabled = true;
995
996 return kStateCalibrating;
997 }
998
999 int SetCurrentRequestInterval(const EventImp &evt)
1000 {
1001 if (!CheckEventSize(evt.GetSize(), "SetCurrentRequestInterval", 2))
1002 return kSM_FatalError;
1003
1004 fCurrentRequestInterval = evt.GetUShort();
1005
1006 Out() << "New current request interval: " << fCurrentRequestInterval << "ms" << endl;
1007
1008 return GetCurrentState();
1009 }
1010
1011 int Execute()
1012 {
1013 // Dispatch (execute) at most one handler from the queue. In contrary
1014 // to run_one(), it doesn't wait until a handler is available
1015 // which can be dispatched, so poll_one() might return with 0
1016 // handlers dispatched. The handlers are always dispatched/executed
1017 // synchronously, i.e. within the call to poll_one()
1018 //poll_one();
1019
1020 if (fStatusDim.second==0)
1021 return kStateDimNetworkNA;
1022
1023 const bool bias = fStatusBias.second >= BIAS::kConnecting;
1024 const bool fad = fStatusFAD.second >= FAD::kConnected;
1025 const bool fsc = fStatusFSC.second >= 2;
1026
1027 // All subsystems are not connected
1028 if (!bias && !fad && !fsc)
1029 return kStateDisconnected;
1030
1031 // At least one subsystem apart from bias is connected
1032 if (bias && !fad && !fsc)
1033 return kStateConnecting;
1034
1035/*
1036 // All subsystems are connected
1037 if (GetCurrentStatus()==kStateConfiguringStep1)
1038 {
1039 if (fCursor<1)
1040 return kStateConfiguringStep1;
1041
1042 if (fCursor==1)
1043 {
1044 fStartTime = Time();
1045 return kStateConfiguringStep2;
1046 }
1047 }
1048 if (GetCurrentStatus()==kStateConfiguringStep2)
1049 {
1050 if (fCursor==1)
1051 {
1052 if ((Time()-fStartTime).total_microseconds()/1000000.<1.5)
1053 return kStateConfiguringStep2;
1054
1055 Dim::SendCommand("BIAS_CONTROL/REQUEST_STATUS");
1056 }
1057 if (fCursor==2)
1058 {
1059
1060 int n=0;
1061 double avg = 0;
1062 for (size_t i=0; i<fCurrents.size(); i++)
1063 if (fCurrents[i]>=0)
1064 {
1065 avg += fCurrents[i];
1066 n++;
1067 }
1068
1069 cout << avg/n << endl;
1070 }
1071 return kStateConnected;
1072 }
1073 */
1074
1075 // Needs connection of FAD and BIAS
1076 if (bias && fad)
1077 {
1078 if (fControlType==kFeedback || fControlType==kFeedbackGlobal)
1079 return fOutputEnabled ? kStateFeedbackCtrlRunning : kStateFeedbackCtrlIdle;
1080 }
1081
1082 // Needs connection of FSC and BIAS
1083 if (bias && fsc)
1084 {
1085 if (fControlType==kTemp)
1086 {
1087 if (GetCurrentState()==kStateCalibrating && fCursorCur<fNumCalibRequests)
1088 return GetCurrentState();
1089
1090 return fOutputEnabled ? kStateTempCtrlRunning : kStateTempCtrlIdle;
1091 }
1092 if (fControlType==kCurrents)
1093 {
1094 static Time past;
1095 if (fCurrentRequestInterval>0 && Time()-past>boost::posix_time::milliseconds(fCurrentRequestInterval))
1096 {
1097 if (fStatusBias.second==BIAS::kVoltageOn)
1098 DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0);
1099 past = Time();
1100 }
1101
1102 return fOutputEnabled ? kStateCurrentCtrlRunning : kStateCurrentCtrlIdle;
1103 }
1104 }
1105
1106 if (bias && fad && !fsc)
1107 return kStateConnectedFAD;
1108
1109 if (bias && fsc && !fad)
1110 return kStateConnectedFSC;
1111
1112 return kStateConnected;
1113 }
1114
1115public:
1116 StateMachineFeedback(ostream &out=cout) : StateMachineDim(out, "FEEDBACK"),
1117 fStatusDim(make_pair(Time(), -2)),
1118 fStatusFAD(make_pair(Time(), -2)),
1119 fStatusBias(make_pair(Time(), -2)),
1120 fDim("DIS_DNS/VERSION_NUMBER", (void*)NULL, 0, this),
1121 fFAD("FAD_CONTROL/STATE", (void*)NULL, 0, this),
1122 fFSC("FSC_CONTROL/STATE", (void*)NULL, 0, this),
1123 fBias("BIAS_CONTROL/STATE", (void*)NULL, 0, this),
1124 fBiasA("BIAS_CONTROL/CURRENT", (void*)NULL, 0, this),
1125 fBiasData("FAD_CONTROL/FEEDBACK_DATA", (void*)NULL, 0, this),
1126 fCameraTemp("FSC_CONTROL/TEMPERATURE", (void*)NULL, 0, this),
1127 fDimReference("FEEDBACK/REFERENCE", "F:416",
1128 "Amplitude reference value(s)"
1129 "Vref[mV]:Amplitude reference"),
1130 fDimDeviation("FEEDBACK/DEVIATION", "F:416;F:416",
1131 "Control loop information"
1132 "|DeltaAmpl[mV]:Amplitude offset measures"
1133 "|DeltaBias[mV]:Correction value calculated"),
1134 fDimCalibration("FEEDBACK/CALIBRATION", "F:416;F:416",
1135 "Current offsets"
1136 "|Avg[dac]:Average offset (5000uA/4096dac)"
1137 "|Rms[dac]:Rms of offset (5000uA/4096dac)"),
1138 fSP(BIAS::kNumChannels),
1139 fKp(0), fKi(0), fKd(0), fT(-1),
1140 fCurrentRequestInterval(0),
1141 fNumCalibIgnore(30),
1142 fNumCalibRequests(300),
1143 fOutputEnabled(false)
1144 {
1145 // ba::io_service::work is a kind of keep_alive for the loop.
1146 // It prevents the io_service to go to stopped state, which
1147 // would prevent any consecutive calls to run()
1148 // or poll() to do nothing. reset() could also revoke to the
1149 // previous state but this might introduce some overhead of
1150 // deletion and creation of threads and more.
1151
1152// fSP.resize(416);
1153
1154 // State names
1155 AddStateName(kStateDimNetworkNA, "DimNetworkNotAvailable",
1156 "The Dim DNS is not reachable.");
1157
1158 AddStateName(kStateDisconnected, "Disconnected",
1159 "The Dim DNS is reachable, but the required subsystems are not available.");
1160
1161 AddStateName(kStateConnecting, "Connecting",
1162 "Only biasctrl is available and connected with its hardware.");
1163
1164 AddStateName(kStateConnectedFSC, "ConnectedFSC",
1165 "biasctrl and fscctrl are available and connected with their hardware.");
1166 AddStateName(kStateConnectedFAD, "ConnectedFAD",
1167 "biasctrl and fadctrl are available and connected with their hardware.");
1168 AddStateName(kStateConnected, "Connected",
1169 "biasctrl, fadctrl and fscctrl are available and connected with their hardware.");
1170
1171 AddStateName(kStateFeedbackCtrlIdle, "FeedbackIdle",
1172 "Feedback control activated, but voltage output disabled.");
1173 AddStateName(kStateTempCtrlIdle, "TempCtrlIdle",
1174 "Temperature control activated, but voltage output disabled.");
1175 AddStateName(kStateCurrentCtrlIdle, "CurrentCtrlIdle",
1176 "Current control activated, but voltage output disabled.");
1177
1178 AddStateName(kStateFeedbackCtrlRunning, "FeedbackControl",
1179 "Feedback control activated and voltage output enabled.");
1180 AddStateName(kStateTempCtrlRunning, "TempControl",
1181 "Temperature control activated and voltage output enabled.");
1182 AddStateName(kStateCurrentCtrlRunning, "CurrentControl",
1183 "Current/Temp control activated and voltage output enabled.");
1184 AddStateName(kStateCalibrating, "Calibrating",
1185 "Calibrating current offsets.");
1186
1187 AddEvent("START_FEEDBACK_CONTROL", "S:1", kStateConnectedFAD, kStateConnected)
1188 (bind(&StateMachineFeedback::StartFeedback, this, placeholders::_1))
1189 ("Start the feedback control loop"
1190 "|Num[short]:Number of events 'medianed' to calculate the correction value");
1191
1192 AddEvent("START_GLOBAL_FEEDBACK", "S:1", kStateConnectedFAD, kStateConnected)
1193 (bind(&StateMachineFeedback::StartFeedbackGlobal, this, placeholders::_1))
1194 ("Start the global feedback control loop"
1195 "Num[short]:Number of events averaged to calculate the correction value");
1196
1197 AddEvent("START_TEMP_CONTROL", "F:1", kStateConnectedFSC, kStateConnected)
1198 (bind(&StateMachineFeedback::StartTempCtrl, this, placeholders::_1))
1199 ("Start the temperature control loop"
1200 "|offset[V]:Offset from the nominal temperature corrected value in Volts");
1201
1202 AddEvent("START_CURRENT_CONTROL", "F:1", kStateConnectedFSC, kStateConnected)
1203 (bind(&StateMachineFeedback::StartCurrentCtrl, this, placeholders::_1))
1204 ("Start the current/temperature control loop"
1205 "|offset[V]:Offset from the nominal current/temperature corrected value in Volts");
1206
1207 // kStateTempCtrlIdle, kStateFeedbackCtrlIdle, kStateTempCtrlRunning, kStateFeedbackCtrlRunning
1208 AddEvent("STOP")
1209 (bind(&StateMachineFeedback::StopFeedback, this))
1210 ("Stop any control loop");
1211
1212 AddEvent("ENABLE_OUTPUT", "B:1")//, kStateIdle)
1213 (bind(&StateMachineFeedback::EnableOutput, this, placeholders::_1))
1214 ("Enable sending of correction values caluclated by the control loop to the biasctrl");
1215
1216 AddEvent("STORE_REFERENCE")//, kStateIdle)
1217 (bind(&StateMachineFeedback::StoreReference, this))
1218 ("Store the last (averaged) value as new reference (for debug purpose only)");
1219
1220 AddEvent("SET_REFERENCE", "F:1")//, kStateIdle)
1221 (bind(&StateMachineFeedback::SetReference, this, placeholders::_1))
1222 ("Set a new global reference value (for debug purpose only)");
1223
1224 AddEvent("SET_Ki", "D:1")//, kStateIdle)
1225 (bind(&StateMachineFeedback::SetConstant, this, placeholders::_1, 0))
1226 ("Set integral constant Ki");
1227
1228 AddEvent("SET_Kp", "D:1")//, kStateIdle)
1229 (bind(&StateMachineFeedback::SetConstant, this, placeholders::_1, 1))
1230 ("Set proportional constant Kp");
1231
1232 AddEvent("SET_Kd", "D:1")//, kStateIdle)
1233 (bind(&StateMachineFeedback::SetConstant, this, placeholders::_1, 2))
1234 ("Set derivative constant Kd");
1235
1236 AddEvent("SET_T", "D:1")//, kStateIdle)
1237 (bind(&StateMachineFeedback::SetConstant, this, placeholders::_1, 3))
1238 ("Set time-constant. (-1 to use the cycle time, i.e. the time for the last average cycle, instead)");
1239
1240 AddEvent("CALIBRATE_CURRENTS", kStateConnectedFSC, kStateConnected)//, kStateIdle)
1241 (bind(&StateMachineFeedback::CalibrateCurrents, this))
1242 ("");
1243
1244 AddEvent("SET_CURRENT_REQUEST_INTERVAL", kStateConnectedFSC, kStateConnected)//, kStateIdle)
1245 (bind(&StateMachineFeedback::SetCurrentRequestInterval, this, placeholders::_1))
1246 ("|interval[ms]:Interval between two current requests in modes which need that.");
1247
1248 // Verbosity commands
1249// AddEvent("SET_VERBOSE", "B:1")
1250// (bind(&StateMachineMCP::SetVerbosity, this, placeholders::_1))
1251// ("set verbosity state"
1252// "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
1253
1254 AddEvent("PRINT")
1255 (bind(&StateMachineFeedback::Print, this))
1256 ("");
1257
1258 AddEvent("PRINT_CALIBRATION")
1259 (bind(&StateMachineFeedback::PrintCalibration, this))
1260 ("");
1261 }
1262
1263 int EvalOptions(Configuration &conf)
1264 {
1265 if (!fMap.Read(conf.Get<string>("pixel-map-file")))
1266 {
1267 Error("Reading mapping table from "+conf.Get<string>("pixel-map-file")+" failed.");
1268 return 1;
1269 }
1270
1271 fGain = 0.1; // V(Amplitude) / V(Bias)
1272
1273 // 148 -> 248
1274
1275 // 33 : 10s < 2%
1276 // 50 : 5s < 2%
1277 // 66 : 3s < 2%
1278 // 85 : 2s < 2%
1279
1280 fKp = 0;
1281 fKd = 0;
1282 fKi = 0.75;
1283 fT = 1;
1284
1285 // Is that independent of the aboslute real amplitude of
1286 // the light pulser?
1287
1288 ostringstream msg;
1289 msg << "Control loop parameters: ";
1290 msg << "Kp=" << fKp << ", Kd=" << fKd << ", Ki=" << fKi << ", ";
1291 if (fT>0)
1292 msg << fT;
1293 else
1294 msg << "<auto>";
1295 msg << ", Gain(DRS/BIAS)=" << fGain << "V/V";
1296
1297 Message(msg);
1298
1299 fCurrentRequestInterval = conf.Get<uint16_t>("current-request-interval");
1300 fNumCalibIgnore = conf.Get<uint16_t>("num-calib-ignore");
1301 fNumCalibRequests = conf.Get<uint16_t>("num-calib-average");
1302
1303 return -1;
1304 }
1305};
1306
1307// ------------------------------------------------------------------------
1308
1309#include "Main.h"
1310
1311template<class T>
1312int RunShell(Configuration &conf)
1313{
1314 return Main::execute<T, StateMachineFeedback>(conf);
1315}
1316
1317void SetupConfiguration(Configuration &conf)
1318{
1319 po::options_description control("Feedback options");
1320 control.add_options()
1321 ("pixel-map-file", var<string>("FACTmapV5a.txt"), "Pixel mapping file. Used here to get the default reference voltage.")
1322 ("current-request-interval", var<uint16_t>(1000), "Interval between two current requests.")
1323 ("num-calib-ignore", var<uint16_t>(30), "Number of current requests to be ignored before averaging")
1324 ("num-calib-average", var<uint16_t>(300), "Number of current requests to be averaged")
1325 ;
1326
1327 conf.AddOptions(control);
1328}
1329
1330/*
1331 Extract usage clause(s) [if any] for SYNOPSIS.
1332 Translators: "Usage" and "or" here are patterns (regular expressions) which
1333 are used to match the usage synopsis in program output. An example from cp
1334 (GNU coreutils) which contains both strings:
1335 Usage: cp [OPTION]... [-T] SOURCE DEST
1336 or: cp [OPTION]... SOURCE... DIRECTORY
1337 or: cp [OPTION]... -t DIRECTORY SOURCE...
1338 */
1339void PrintUsage()
1340{
1341 cout <<
1342 "The feedback control the BIAS voltages based on the calibration signal.\n"
1343 "\n"
1344 "The default is that the program is started without user intercation. "
1345 "All actions are supposed to arrive as DimCommands. Using the -c "
1346 "option, a local shell can be initialized. With h or help a short "
1347 "help message about the usuage can be brought to the screen.\n"
1348 "\n"
1349 "Usage: feedback [-c type] [OPTIONS]\n"
1350 " or: feedback [OPTIONS]\n";
1351 cout << endl;
1352}
1353
1354void PrintHelp()
1355{
1356 Main::PrintHelp<StateMachineFeedback>();
1357
1358 /* Additional help text which is printed after the configuration
1359 options goes here */
1360
1361 /*
1362 cout << "bla bla bla" << endl << endl;
1363 cout << endl;
1364 cout << "Environment:" << endl;
1365 cout << "environment" << endl;
1366 cout << endl;
1367 cout << "Examples:" << endl;
1368 cout << "test exam" << endl;
1369 cout << endl;
1370 cout << "Files:" << endl;
1371 cout << "files" << endl;
1372 cout << endl;
1373 */
1374}
1375
1376int main(int argc, const char* argv[])
1377{
1378 Configuration conf(argv[0]);
1379 conf.SetPrintUsage(PrintUsage);
1380 Main::SetupConfiguration(conf);
1381 SetupConfiguration(conf);
1382
1383 if (!conf.DoParse(argc, argv, PrintHelp))
1384 return -1;
1385
1386 //try
1387 {
1388 // No console access at all
1389 if (!conf.Has("console"))
1390 {
1391// if (conf.Get<bool>("no-dim"))
1392// return RunShell<LocalStream, StateMachine, ConnectionFSC>(conf);
1393// else
1394 return RunShell<LocalStream>(conf);
1395 }
1396 // Cosole access w/ and w/o Dim
1397/* if (conf.Get<bool>("no-dim"))
1398 {
1399 if (conf.Get<int>("console")==0)
1400 return RunShell<LocalShell, StateMachine, ConnectionFSC>(conf);
1401 else
1402 return RunShell<LocalConsole, StateMachine, ConnectionFSC>(conf);
1403 }
1404 else
1405*/ {
1406 if (conf.Get<int>("console")==0)
1407 return RunShell<LocalShell>(conf);
1408 else
1409 return RunShell<LocalConsole>(conf);
1410 }
1411 }
1412 /*catch (std::exception& e)
1413 {
1414 cerr << "Exception: " << e.what() << endl;
1415 return -1;
1416 }*/
1417
1418 return 0;
1419}
Note: See TracBrowser for help on using the repository browser.