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

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