#include #include "Dim.h" #include "Event.h" #include "Shell.h" #include "StateMachineDim.h" #include "Connection.h" #include "Configuration.h" #include "Console.h" #include "Converter.h" #include "externals/PixelMap.h" #include "tools.h" #include "LocalControl.h" #include "HeadersFAD.h" #include "HeadersFSC.h" #include "HeadersBIAS.h" #include "HeadersFeedback.h" #include "DimState.h" #include "DimDescriptionService.h" using namespace std; // ------------------------------------------------------------------------ class StateMachineFeedback : public StateMachineDim { private: enum control_t { kIdle, kTemp, kFeedback, kFeedbackGlobal, kCurrents, kCurrentsNew, }; control_t fControlType; PixelMap fMap; DimVersion fDim; DimDescribedState fDimFAD; DimDescribedState fDimFSC; DimDescribedState fDimBias; DimDescribedService fDimReference; DimDescribedService fDimDeviation; DimDescribedService fDimCalibration; DimDescribedService fDimCurrents; vector fCurrentsAvg; vector fCurrentsRms; vector fCalibration; vector fVoltGapd; vector fBiasVolt; vector> fData; int64_t fCursorCur; uint64_t fCursorAmpl; uint64_t fCursorTemp; Time fBiasLast; Time fStartTime; Time fCalibTime; valarray fPV[3]; // Process variable (intgerated/averaged amplitudes) valarray fSP; // Set point (target amplitudes) double fKp; // Proportional constant double fKi; // Integral constant double fKd; // Derivative constant double fT; // Time constant (cycle time) double fGain; // Gain (conversion from a DRS voltage deviation into a BIAS voltage change at G-APD reference voltage) double fT21; double fBiasOffset; double fTempOffset; double fCalibrationOffset; double fAppliedOffset; uint16_t fCurrentRequestInterval; uint16_t fNumCalibIgnore; uint16_t fNumCalibRequests; bool fOutputEnabled; int HandleCameraTemp(const EventImp &evt) { if (fControlType!=kTemp && fControlType!=kCurrents && fControlType!=kCurrentsNew) return GetCurrentState(); if (evt.GetSize()!=60*sizeof(float)) return GetCurrentState(); const float *ptr = evt.Ptr(); double avgt = 0; int numt = 0; for (int i=1; i<32; i++) if (ptr[i]!=0) { avgt += ptr[i]; numt++; } if (numt==0) { Warn("Received sensor temperatures all invalid."); return GetCurrentState(); } avgt /= numt; // [deg C] fTempOffset = (avgt-25)*4./70; // [V] fCursorTemp++; return fControlType==kCurrentsNew ? HandleCurrentControlNew() : HandleCurrentControl(); } int HandleCurrentControlNew() { if (GetCurrentState()==Feedback::State::kCalibrating && fBiasOffset>fTempOffset-1.2) { fCursorTemp = 0; ostringstream msg; msg << " (applied calibration offset " << fBiasOffset << "V exceeds temperature correction " << fTempOffset << "V - 1.2V."; Warn("Trying to calibrate above G-APD breakdown volatge!"); Warn(msg); return GetCurrentState(); } double avg[2] = { 0, 0 }; double min[2] = { 90, 90 }; double max[2] = { -90, -90 }; int num[2] = { 0, 0 }; vector med[2]; med[0].resize(416); med[1].resize(416); const float *Ravg = fCalibration.data()+BIAS::kNumChannels*2; // Measured resistance vector vec(2*BIAS::kNumChannels+2); vec[BIAS::kNumChannels*2] = fTempOffset; vec[BIAS::kNumChannels*2+1] = fBiasOffset; float *Uoff = vec.data()+BIAS::kNumChannels; if (GetCurrentState()==Feedback::State::kCalibrating) for (int i=0; iUbd) { // The differential resistance of the G-APD, i.e. the dependence of the // current above the breakdown voltage, is given by const double Rapd = Uov/Iapd; // This allows us to estimate the current Iov at the overvoltage we want to apply const double Iov = (1.1+fBiasOffset)/Rapd; // This gives us an ohmic resistance Rov of the G-APD at the set-point const double Rest = (Ubd+1.1+fBiasOffset)/Iov; // This lets us estimate the total resistance Rtot of the circuit at the set-point const double R3b = R4 + (R5+Rest)/N; const double Rtot = R2 + 1/(1/R1 + 1/R3b); // From this we can estimate the output voltage we need to get the // over-voltage at the G-APD as anticipated const double r = 1 + R3/R1 - (R2 + R3 + R3*R2/R1)/Rtot; const double Uset = (Ubd+1.1+fBiasOffset)/r; Uoff[i] = Uset - fVoltGapd[i]; } // Calculate statistics only for channels with a valid calibration if (Uov>0) { const int g = hv.group(); med[g][num[g]] = Uov; avg[g] += Uov; num[g]++; if (Uovmax[g]) max[g] = Uov; } } sort(med[0].begin(), med[0].begin()+num[0]); sort(med[1].begin(), med[1].begin()+num[1]); fCurrentsAvg.assign(BIAS::kNumChannels, 0); fCursorCur = 0; } fDimDeviation.setQuality(fControlType); fDimDeviation.Update(vec); // Warning: Here it is assumed that the ramp up and down is done properly // within the time between two new temperatures and that the calibration // is finished within that time. if (GetCurrentState()!=Feedback::State::kCalibrating || fDimBias.state()!=BIAS::State::kVoltageOff || fCursorTemp!=1 || !fOutputEnabled) { if (!fOutputEnabled || fDimBias.state()!=BIAS::State::kVoltageOn) return GetCurrentState(); // Trigger calibration if (GetCurrentState()==Feedback::State::kCalibrating && fCursorTemp==2) { DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0); return GetCurrentState(); } } ostringstream msg; 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."; Info(msg); if (fControlType==kCurrents && num[0]>0 && num[1]>0) { msg.str(""); msg << " Avg0=" << setw(7) << avg[0]/num[0] << " | Avg1=" << setw(7) << avg[1]/num[1]; Debug(msg); msg.str(""); msg << " Med0=" << setw(7) << med[0][num[0]/2] << " | Med1=" << setw(7) << med[1][num[1]/2]; Debug(msg); msg.str(""); msg << " Min0=" << setw(7) << min[0] << " | Min1=" << setw(7) << min[1]; Debug(msg); msg.str(""); msg << " Max0=" << setw(7) << max[0] << " | Max1=" << setw(7) << max[1]; Debug(msg); } DimClient::sendCommandNB("BIAS_CONTROL/SET_ALL_CHANNELS_OFFSET", vec.data()+BIAS::kNumChannels, BIAS::kNumChannels*sizeof(float)); return GetCurrentState(); } int HandleCurrentControl() { const double dUt = fTempOffset; // [V] if (GetCurrentState()==Feedback::State::kCalibrating && fBiasOffset>dUt-1.2) { fCursorTemp = 0; ostringstream msg; msg << " (applied calibration offset " << fBiasOffset << "V exceeds temperature correction " << fTempOffset << "V - 1.2V."; Warn("Trying to calibrate above G-APD breakdown volatge!"); Warn(msg); return GetCurrentState(); } // FIXME: If calibrating do not wait for the temperature! fAppliedOffset = fBiasOffset; if (GetCurrentState()!=Feedback::State::kCalibrating) fAppliedOffset += dUt; vector vec(2*BIAS::kNumChannels+2); for (int i=0; i med[2]; med[0].resize(416); med[1].resize(416); if (fControlType==kCurrents) { if (fCursorCur==0) { //DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0); return GetCurrentState(); } // Pixel 583: 5 31 == 191 (5) C2 B3 P3 // Pixel 830: 2 2 == 66 (4) C0 B8 P1 // Pixel 1401: 6 1 == 193 (5) C2 B4 P0 // Convert from DAC counts to uA const double conv = 5000./4096; // 3900 Ohm/n + 1000 Ohm + 1100 Ohm (with n=4 or n=5) const double R[2] = { 3075, 2870 }; const float *Iavg = fCalibration.data(); // Offset at U=fCalibrationOffset const float *Ravg = fCalibration.data()+BIAS::kNumChannels*2; // Measured resistance // U0 = fCalibrationOffset // dT = fAppliedVoltage // Ifeedback = Im[i] - (U[i]-U0)/Ravg[i] - Iavg[i]; // dUapplied[i] + dUneu[i] = R[g] * (Im[i] - (dUapplied[i]+dUneu[i]-U0+dT)/Ravg[i] - Iavg[i]) // The assumption here is that the offset calculated from the temperature // does not significanly change within a single step // dU[i] := dUtotal[i] = dUapplied[i] + dUneu[i] // dU[i] / R[g] = Im[i] - (dU[i]+dT-U0)/Ravg[i] - Iavg[i] // dU[i]/R[g] + dU[i]/Ravg[i] = Im[i] + U0/Ravg[i] - dT/Ravg[i] - Iavg[i] // dU[i]*(1/R[g]+1/Ravg[i]) = Im[i] - Iavg[i] + U0/Ravg[i] - dT/Ravg[i] // dU[i] = (Im[i] - Iavg[i] + U0/Ravg[i] - dT/Ravg[i]) / (1/R[g]+1/Ravg[i]) // dU[i] = { Im[i] - Iavg[i] + (U0-dT)/Ravg[i] } * r with r := 1 / (1/R[g]+1/Ravg[i]) const double U0 = fAppliedOffset-fCalibrationOffset; for (int i=0; iIavg[i]) and we operate above the calibration point) const double I = Im>Iavg[i] ? Im - Iavg[i] : 0; // [A] // Make sure that the averaged resistor is valid const double dU = Ravg[i]>10000 ? r*(I*1e-6 - dI) : 0; vec[i+BIAS::kNumChannels] += dU; // Angelegte Spannung: U0+dU // Gemessener Strom: Im - Iavg // Strom offset: (U0+dU) / Ravg // Fliessender Strom: Im-Iavg - (U0+dU)/Ravg // Korrektur: [ Im-Iavg - (U0+dU)/Ravg ] * Rg // Aufgeloest nach dU: dU = ( Im-Iavg - dU/Ravg ) / ( 1/Rg + 1/Ravg ) // Equivalent zu: dU = ( I*Ravg - U0 ) / ( Ravg/Rg+1 ) // Calculate statistics only for channels with a valid calibration if (Iavg[i]>0) { med[g][num[g]] = dU; avg[g] += dU; num[g]++; if (dUmax[g]) max[g] = dU; } } sort(med[0].begin(), med[0].begin()+num[0]); sort(med[1].begin(), med[1].begin()+num[1]); fCurrentsAvg.assign(BIAS::kNumChannels, 0); fCursorCur = 0; } fDimDeviation.setQuality(fControlType); fDimDeviation.Update(vec); // Warning: Here it is assumed that the ramp up and down is done properly // within the time between two new temperatures and that the calibration // is finished within that time. if (!(GetCurrentState()==Feedback::State::kCalibrating && fCursorTemp==1 && fOutputEnabled && fDimBias.state()==BIAS::State::kVoltageOff)) { if (!fOutputEnabled || fDimBias.state()!=BIAS::State::kVoltageOn) return GetCurrentState(); // Trigger calibration if (GetCurrentState()==Feedback::State::kCalibrating && fCursorTemp==2) { DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0); return GetCurrentState(); } } ostringstream msg; 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."; Info(msg); if (fControlType==kCurrents && num[0]>0 && num[1]>0) { msg.str(""); msg << " Avg0=" << setw(7) << avg[0]/num[0] << " | Avg1=" << setw(7) << avg[1]/num[1]; Debug(msg); msg.str(""); msg << " Med0=" << setw(7) << med[0][num[0]/2] << " | Med1=" << setw(7) << med[1][num[1]/2]; Debug(msg); msg.str(""); msg << " Min0=" << setw(7) << min[0] << " | Min1=" << setw(7) << min[1]; Debug(msg); msg.str(""); msg << " Max0=" << setw(7) << max[0] << " | Max1=" << setw(7) << max[1]; Debug(msg); } DimClient::sendCommandNB("BIAS_CONTROL/SET_ALL_CHANNELS_OFFSET", vec.data()+BIAS::kNumChannels, BIAS::kNumChannels*sizeof(float)); return GetCurrentState(); } int AverageCurrents(const EventImp &evt) { if (evt.GetSize()!=BIAS::kNumChannels*sizeof(int16_t)) return -1; if (fDimBias.state()!=BIAS::State::kVoltageOn) return false; if (fCursorCur++<0) return true; const int16_t *ptr = evt.Ptr(); for (int i=0; iboost::posix_time::seconds(30)) { Warn("Last received event data older than 30s... resetting average calculation."); ResetData(); } fBiasLast = tm; // -------- Store new event -------- fData[fCursorAmpl%fData.size()].assign(evt.Ptr(), evt.Ptr()+1440); if (++fCursorAmpl med(1440); for (int ch=0; ch<1440; ch++) { vector arr(fData.size()); for (size_t i=0; i med(1440); vector rms(1440); for (size_t i=0; i avg(BIAS::kNumChannels); vector num(BIAS::kNumChannels); for (int i=0; i<1440; i++) { const PixelMapEntry &ch = fMap.hw(i); // FIXME: Add a consistency check if the median makes sense... // FIXME: Add a consistency check to remove pixels with bright stars (median?) avg[ch.hv()] += med[i]; num[ch.hv()]++; } for (int i=0; i0) return; // FIXME: Take out broken / dead boards. const Time tm0 = Time(); /*const*/ double T21 = fT>0 ? fT : (tm0-fStartTime).total_microseconds()/1000000.; const double T10 = fT21; fT21 = T21; fStartTime = tm0; ostringstream out; out << "New " << fData.size() << " event received: " << fCursorAmpl << " / " << setprecision(3) << T21 << "s"; Info(out); if (fPV[0].size()==0) { fPV[0].resize(avg.size()); fPV[0] = valarray(avg.data(), avg.size()); return; } if (fPV[1].size()==0) { fPV[1].resize(avg.size()); fPV[1] = valarray(avg.data(), avg.size()); return; } if (fPV[2].size()==0) { fPV[2].resize(avg.size()); fPV[2] = valarray(avg.data(), avg.size()); return; } fPV[0] = fPV[1]; fPV[1] = fPV[2]; fPV[2].resize(avg.size()); fPV[2] = valarray(avg.data(), avg.size()); if (T10<=0 || T21<=0) return; //cout << "Calculating (" << fCursor << ":" << T21 << ")... " << endl; // fKi[j] = response[j]*gain; // Kp = 0; // Kd = 0; // => Kp = 0.01 * gain = 0.00005 // => Ki = 0.8 * gain/20s = 0.00025 // => Kd = 0.1 * gain/20s = 0.00003 /* fKp = 0; fKd = 0; fKi = 0.00003*20; T21 = 1; */ //valarray correction = - Kp*(PV[2] - PV[1]) + Ki * dT * (SP-PV[2]) - Kd/dT * (PV[2] - 2*PV[1] + PV[0]); //valarray correction = // - Kp * (PV[2] - PV[1]) // + dT * Ki * (SP - PV[2]) // - Kd / dT * (PV[2] - 2*PV[1] + PV[0]); // // - (Kp+Kd/dT1) * (PV[2] - PV[1]) // + dT2 * Ki * (SP - PV[2]) // + Kd / dT1 * (PV[1] - PV[0]); // // - Kp * (PV[2] - PV[1]) // + Ki * (SP - PV[2])*dT // - Kd * (PV[2] - PV[1])/dT // + Kd * (PV[1] - PV[0])/dT; // //valarray correction = // - Kp*(PV[2] - PV[1]) + Ki * T21 * (SP-PV[2]) - Kd*(PV[2]-PV[1])/T21 - Kd*(PV[0]-PV[1])/T01; const valarray correction = 1./fGain/1000* ( - (fKp+fKd/T21)*(fPV[2] - fPV[1]) + fKi*T21*(fSP-fPV[2]) + fKd/T10*(fPV[1]-fPV[0]) ); /* integral = 0 start: integral += (fSP - fPV[2])*dt output = Kp*(fSP - fPV[2]) + Ki*integral - Kd*(fPV[2] - fPV[1])/dt wait(dt) goto start */ vector vec(2*BIAS::kNumChannels+2); for (int i=0; i arr(evt.Ptr(), evt.Ptr()+1440); sort(arr.begin(), arr.end()); const float med = arr[arr.size()/2]; fData[fCursorAmpl%fData.size()].resize(1); //assign(&med, &med); fData[fCursorAmpl%fData.size()][0] = med; //assign(&med, &med); if (++fCursorAmpl(&avg, 1); return; } if (fPV[1].size()==0) { fPV[1].resize(1); fPV[1] = valarray(&avg, 1); return; } if (fPV[2].size()==0) { fPV[2].resize(1); fPV[2] = valarray(&avg, 1); return; } fPV[0] = fPV[1]; fPV[1] = fPV[2]; fPV[2].resize(1); fPV[2] = valarray(&avg, 1); // ----- Calculate average currents ----- vector A(BIAS::kNumChannels); for (int i=0; i correction = 1./fGain/1000* ( - (fKp+fKd/T21)*(fPV[2] - fPV[1]) + fKi*T21*(fSP[0]-fPV[2]) + fKd/T10*(fPV[1]-fPV[0]) ); */ // pow of 1.6 comes from the non-linearity of the // amplitude vs bias voltage const valarray correction = 1./fGain/1000* ( //fKi*(pow(fSP[0], 1./1.6)-pow(fPV[2], 1./1.6)) fKi*(fSP[0]-fPV[2]) ); Out() << "Correction: " << correction[0] << "V (" << fSP[0] << ")" << endl; const int nch = BIAS::kNumChannels; // FIXME: Sanity check! vector vec; vec.reserve(2*nch+2); vec.insert(vec.begin(), nch, fPV[2][0]-fSP[0]); vec.insert(vec.begin()+nch, nch, correction[0]); vec.push_back(0); vec.push_back(0); fDimDeviation.setQuality(fControlType); fDimDeviation.Update(vec); if (!fOutputEnabled || fDimBias.state()!=BIAS::State::kVoltageOn) return; Info("Sending new global relative offset to biasctrl."); DimClient::sendCommandNB("BIAS_CONTROL/INCREASE_ALL_CHANNELS_VOLTAGE", vec.data()+BIAS::kNumChannels, BIAS::kNumChannels*sizeof(float)); } void HandleCalibrateCurrents(const EventImp &evt) { if (fBiasVolt.empty() || fCalibration.empty() || evt.GetSize()<416*sizeof(int16_t)) return; struct dim_data { float I[416]; float Iavg; float Irms; float Imed; float Idev; uint32_t N; float Tdiff; dim_data() { memset(this, 0, sizeof(dim_data)); } } __attribute__((__packed__)); const int16_t *I = evt.Ptr(); const float *R = fCalibration.data()+BIAS::kNumChannels*2; const float *U = fBiasVolt.data(); vector med(416); uint16_t cnt = 0; double avg = 0; double rms = 0; dim_data data; for (int i=0; i<416; i++) { const PixelMapEntry &hv = fMap.hv(i); if (!hv) continue; if (R[i]<=0) continue; data.I[i] = I[i]*5000./4096 - U[i]/R[i]*1e6; data.I[i] /= hv.group() ? 5 : 4; avg += data.I[i]; rms += data.I[i]*data.I[i]; if (i>=320) continue; med[cnt++] = data.I[i]; } if (cnt==0) return; avg /= cnt; rms /= cnt; data.N = cnt; data.Iavg = avg; data.Irms = sqrt(rms-avg*avg); sort(med.data(), med.data()+cnt); data.Imed = cnt%2 ? (med[cnt/2-1]+med[cnt/2])/2 : med[cnt/2]; for (int i=0; i0 && fCursorCur>0) { // fCursorTemp: 1 2 3 4 5 6 7 8 // fCursor%x: 1 1 1 2 2 2 3 3 // 9 steps in ~15s //if (fCursorTemp<3 && fCursorCur%(fCursorTemp/3+1)==0) HandleCurrentControl(); }*/ HandleCalibrateCurrents(evt); return GetCurrentState(); } int HandleBiasData(const EventImp &evt) { if (fControlType==kFeedback) HandleFeedback(evt); if (fControlType==kFeedbackGlobal) HandleGlobalFeedback(evt); return GetCurrentState(); } int HandleBiasNom(const EventImp &evt) { if (evt.GetSize()>=416*sizeof(float)) { fVoltGapd.assign(evt.Ptr(), evt.Ptr()+416); Info("Nominal bias voltages received."); } return GetCurrentState(); } int HandleBiasVoltage(const EventImp &evt) { if (evt.GetSize()>=416*sizeof(float)) fBiasVolt.assign(evt.Ptr(), evt.Ptr()+416); return GetCurrentState(); } bool CheckEventSize(size_t has, const char *name, size_t size) { if (has==size) return true; ostringstream msg; msg << name << " - Received event has " << has << " bytes, but expected " << size << "."; Fatal(msg); return false; } int Print() const { Out() << fDim << endl; Out() << fDimFAD << endl; Out() << fDimFSC << endl; Out() << fDimBias << endl; return GetCurrentState(); } int PrintCalibration() { if (fCalibration.empty()) { Out() << "No calibration performed so far." << endl; return GetCurrentState(); } const float *avg = fCalibration.data(); const float *rms = fCalibration.data()+BIAS::kNumChannels; const float *res = fCalibration.data()+BIAS::kNumChannels*2; Out() << "Average current at " << fCalibrationOffset << "V below G-APD operation voltage:\n"; for (int k=0; k<13; k++) for (int j=0; j<8; j++) { Out() << setw(2) << k << "|" << setw(2) << j*4 << "|"; for (int i=0; i<4; i++) Out() << Tools::Form(" %6.1f+-%4.1f", avg[k*32+j*4+i], rms[k*32+j*4+i]); Out() << '\n'; } Out() << '\n'; Out() << "Measured calibration resistor:\n"; for (int k=0; k<13; k++) for (int j=0; j<4; j++) { Out() << setw(2) << k << "|" << setw(2) << j*8 << "|"; for (int i=0; i<8; i++) Out() << Tools::Form(" %5.0f", res[k*32+j*8+i]); Out() << '\n'; } Out() << flush; return GetCurrentState(); } void WarnState(bool needfsc, bool needfad) { const bool bias = fDimBias.state() >= BIAS::State::kConnecting; const bool fsc = fDimFSC.state() >= FSC::State::kConnected; const bool fad = fDimFAD.state() >= FAD::State::kConnected; if (!bias) Warn("Bias control not yet ready."); if (needfsc && !fsc) Warn("FSC control not yet ready."); if (needfad && !fad) Warn("FAD control not yet ready."); } int SetConstant(const EventImp &evt, int constant) { if (!CheckEventSize(evt.GetSize(), "SetConstant", 8)) return kSM_FatalError; switch (constant) { case 0: fKi = evt.GetDouble(); break; case 1: fKp = evt.GetDouble(); break; case 2: fKd = evt.GetDouble(); break; case 3: fT = evt.GetDouble(); break; case 4: fGain = evt.GetDouble(); break; default: Fatal("SetConstant got an unexpected constant id -- this is a program bug!"); return kSM_FatalError; } return GetCurrentState(); } int EnableOutput(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "EnableOutput", 1)) return kSM_FatalError; fOutputEnabled = evt.GetBool(); if (fControlType==kCurrents || fControlType==kCurrentsNew) if (fCursorTemp>1) fCursorTemp = 1; return GetCurrentState(); } void ResetData(int16_t n=-1) { fData.assign(n>0 ? n : fData.size(), vector(0)); fCursorAmpl = 0; fCursorCur = 0; fCursorTemp = 0; fStartTime = Time(); fSP = valarray(0., BIAS::kNumChannels); vector vec(2*BIAS::kNumChannels+2, fBiasOffset); vec[2*BIAS::kNumChannels] = 0; fDimDeviation.setQuality(kIdle); fDimDeviation.Update(vec); fPV[0].resize(0); fPV[1].resize(0); fPV[2].resize(0); fCurrentsAvg.assign(BIAS::kNumChannels, 0); fCurrentsRms.assign(BIAS::kNumChannels, 0); if (fKp==0 && fKi==0 && fKd==0) Warn("Control loop parameters are all set to zero."); } int StartFeedback(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "StartFeedback", 2)) return kSM_FatalError; WarnState(false, true); fBiasOffset = 0; ResetData(evt.GetShort()); fControlType = kFeedback; return GetCurrentState(); } int StartFeedbackGlobal(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "StartFeedbackGlobal", 2)) return kSM_FatalError; WarnState(false, true); fBiasOffset = 0; ResetData(evt.GetShort()); fControlType = kFeedbackGlobal; return GetCurrentState(); } int StartTempCtrl(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "StartTempCtrl", 4)) return kSM_FatalError; WarnState(true, false); fBiasOffset = evt.GetFloat(); fControlType = kTemp; ostringstream out; out << "Starting temperature feedback with an offset of " << fBiasOffset << "V"; Message(out); if (fDimBias.state()==BIAS::State::kVoltageOn) DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0); return GetCurrentState(); } int StartCurrentCtrl(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "StartCurrentCtrl", 4)) return kSM_FatalError; if (fCalibration.empty()) { Warn("Current control needs a bias crate calibration first... command ignored."); return GetCurrentState(); } WarnState(true, false); fBiasOffset = evt.GetFloat(); fTempOffset = -3; ResetData(0); fControlType = kCurrents; ostringstream out; out << "Starting current/temp feedback with an offset of " << fBiasOffset << "V"; Message(out); return GetCurrentState(); } int StartNewCurrentCtrl(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "StartNewCurrentCtrl", 4)) return kSM_FatalError; if (fCalibration.empty()) { Warn("Current control needs a bias crate calibration first... command ignored."); return GetCurrentState(); } WarnState(true, false); fBiasOffset = evt.GetFloat(); fTempOffset = -3; ResetData(0); fControlType = kCurrentsNew; ostringstream out; out << "Starting new current/temp feedback with an offset of " << fBiasOffset << "V"; Message(out); return GetCurrentState(); } int StopFeedback() { fControlType = kIdle; return GetCurrentState(); } int StoreReference() { if (!fPV[0].size() && !fPV[1].size() && !fPV[2].size()) { Warn("No values in memory. Take enough events first!"); return GetCurrentState(); } // FIXME: Check age if (!fPV[1].size() && !fPV[2].size()) fSP = fPV[0]; if (!fPV[2].size()) fSP = fPV[1]; else fSP = fPV[2]; vector vec(BIAS::kNumChannels); for (int i=0; i vec(BIAS::kNumChannels); for (int i=0; i= BIAS::State::kConnecting; const bool fad = fDimFAD.state() >= FAD::State::kConnected; const bool fsc = fDimFSC.state() >= FSC::State::kConnected; // All subsystems are not connected if (!bias && !fad && !fsc) return Feedback::State::kDisconnected; // At least one subsystem apart from bias is connected if (bias && !fad && !fsc) return Feedback::State::kConnecting; /* // All subsystems are connected if (GetCurrentStatus()==Feedback::State::kConfiguringStep1) { if (fCursor<1) return Feedback::State::kConfiguringStep1; if (fCursor==1) { fStartTime = Time(); return Feedback::State::kConfiguringStep2; } } if (GetCurrentStatus()==Feedback::State::kConfiguringStep2) { if (fCursor==1) { if ((Time()-fStartTime).total_microseconds()/1000000.<1.5) return Feedback::State::kConfiguringStep2; Dim::SendCommand("BIAS_CONTROL/REQUEST_STATUS"); } if (fCursor==2) { int n=0; double avg = 0; for (size_t i=0; i=0) { avg += fCurrents[i]; n++; } cout << avg/n << endl; } return Feedback::State::kConnected; } */ // Needs connection of FAD and BIAS if (bias && fad) { if (fControlType==kFeedback || fControlType==kFeedbackGlobal) return fOutputEnabled ? Feedback::State::kFeedbackCtrlRunning : Feedback::State::kFeedbackCtrlIdle; } // Needs connection of FSC and BIAS if (bias && fsc) { if (fControlType==kTemp) { if (GetCurrentState()==Feedback::State::kCalibrating && fCursorCur0 && Time()-past>boost::posix_time::milliseconds(fCurrentRequestInterval)) { if (fDimBias.state()==BIAS::State::kVoltageOn) DimClient::sendCommandNB("BIAS_CONTROL/REQUEST_STATUS", NULL, 0); past = Time(); } return fOutputEnabled && fCursorTemp>0 ? Feedback::State::kCurrentCtrlRunning : Feedback::State::kCurrentCtrlIdle; } } if (bias && fad && !fsc) return Feedback::State::kConnectedFAD; if (bias && fsc && !fad) return Feedback::State::kConnectedFSC; return Feedback::State::kConnected; } public: StateMachineFeedback(ostream &out=cout) : StateMachineDim(out, "FEEDBACK"), //--- fDimFAD("FAD_CONTROL"), fDimFSC("FSC_CONTROL"), fDimBias("BIAS_CONTROL"), //--- fDimReference("FEEDBACK/REFERENCE", "F:416", "Amplitude reference value(s)" "Vref[mV]:Amplitude reference"), fDimDeviation("FEEDBACK/DEVIATION", "F:416;F:416;F:1;F:1", "Control loop information" "|DeltaAmpl[mV]:Amplitude offset measures" "|DeltaBias[mV]:Correction value calculated" "|DeltaTemp[mV]:Correction calculated from temperature" "|DeltaUser[mV]:Additional offset specified by user"), fDimCalibration("FEEDBACK/CALIBRATION", "F:416;F:416;F:416", "Current offsets" "|Avg[uA]:Average offset" "|Rms[uA]:Rms of offset" "|R[Ohm]:Measured calibration resistor"), fDimCurrents("FEEDBACK/CALIBRATED_CURRENTS", "F:416;F:1;F:1;F:1;F:1;I:1;F:1", "Calibrated currents" "|I[uA]:Calibrated currents" "|I_avg[uA]:Average calibrated current (320 channels)" "|I_rms[uA]:Rms of calibrated current (320 channels)" "|I_med[uA]:Median calibrated current (320 channels)" "|I_dev[uA]:Deviation of calibrated current (320 channels)" "|N[uint16]:Number of valid values" "|T_diff[s]:Time difference to calibration"), fSP(BIAS::kNumChannels), fKp(0), fKi(0), fKd(0), fT(-1), fCalibrationOffset(-3), fCurrentRequestInterval(0), fNumCalibIgnore(30), fNumCalibRequests(300), fOutputEnabled(false) { // ba::io_service::work is a kind of keep_alive for the loop. // It prevents the io_service to go to stopped state, which // would prevent any consecutive calls to run() // or poll() to do nothing. reset() could also revoke to the // previous state but this might introduce some overhead of // deletion and creation of threads and more. fDim.Subscribe(*this); fDimFAD.Subscribe(*this); fDimFSC.Subscribe(*this); fDimBias.Subscribe(*this); Subscribe("BIAS_CONTROL/CURRENT") (bind(&StateMachineFeedback::HandleBiasCurrent, this, placeholders::_1)); Subscribe("BIAS_CONTROL/VOLTAGE") (bind(&StateMachineFeedback::HandleBiasVoltage, this, placeholders::_1)); Subscribe("BIAS_CONTROL/FEEDBACK_DATA") (bind(&StateMachineFeedback::HandleBiasData, this, placeholders::_1)); Subscribe("BIAS_CONTROL/NOMINAL") (bind(&StateMachineFeedback::HandleBiasNom, this, placeholders::_1)); Subscribe("FSC_CONTROL/TEMPERATURE") (bind(&StateMachineFeedback::HandleCameraTemp, this, placeholders::_1)); // State names AddStateName(Feedback::State::kDimNetworkNA, "DimNetworkNotAvailable", "The Dim DNS is not reachable."); AddStateName(Feedback::State::kDisconnected, "Disconnected", "The Dim DNS is reachable, but the required subsystems are not available."); AddStateName(Feedback::State::kConnecting, "Connecting", "Only biasctrl is available and connected with its hardware."); AddStateName(Feedback::State::kConnectedFSC, "ConnectedFSC", "biasctrl and fscctrl are available and connected with their hardware."); AddStateName(Feedback::State::kConnectedFAD, "ConnectedFAD", "biasctrl and fadctrl are available and connected with their hardware."); AddStateName(Feedback::State::kConnected, "Connected", "biasctrl, fadctrl and fscctrl are available and connected with their hardware."); AddStateName(Feedback::State::kFeedbackCtrlIdle, "FeedbackIdle", "Feedback control activated, but voltage output disabled."); AddStateName(Feedback::State::kTempCtrlIdle, "TempCtrlIdle", "Temperature control activated, but voltage output disabled."); AddStateName(Feedback::State::kCurrentCtrlIdle, "CurrentCtrlIdle", "Current control activated, but voltage output disabled."); AddStateName(Feedback::State::kFeedbackCtrlRunning, "FeedbackControl", "Feedback control activated and voltage output enabled."); AddStateName(Feedback::State::kTempCtrlRunning, "TempControl", "Temperature control activated and voltage output enabled."); AddStateName(Feedback::State::kCurrentCtrlRunning, "CurrentControl", "Current/Temp control activated and voltage output enabled."); AddStateName(Feedback::State::kCalibrating, "Calibrating", "Calibrating current offsets."); AddEvent("START_FEEDBACK_CONTROL", "S:1", Feedback::State::kConnectedFAD, Feedback::State::kConnected) (bind(&StateMachineFeedback::StartFeedback, this, placeholders::_1)) ("Start the feedback control loop" "|Num[short]:Number of events 'medianed' to calculate the correction value"); AddEvent("START_GLOBAL_FEEDBACK", "S:1", Feedback::State::kConnectedFAD, Feedback::State::kConnected) (bind(&StateMachineFeedback::StartFeedbackGlobal, this, placeholders::_1)) ("Start the global feedback control loop" "Num[short]:Number of events averaged to calculate the correction value"); AddEvent("START_TEMP_CONTROL", "F:1", Feedback::State::kConnectedFSC, Feedback::State::kConnected) (bind(&StateMachineFeedback::StartTempCtrl, this, placeholders::_1)) ("Start the temperature control loop" "|offset[V]:Offset from the nominal temperature corrected value in Volts"); AddEvent("START_CURRENT_CONTROL", "F:1", Feedback::State::kConnectedFSC, Feedback::State::kConnected) (bind(&StateMachineFeedback::StartCurrentCtrl, this, placeholders::_1)) ("Start the current/temperature control loop" "|offset[V]:Offset from the nominal current/temperature corrected value in Volts"); // Feedback::State::kTempCtrlIdle, Feedback::State::kFeedbackCtrlIdle, Feedback::State::kTempCtrlRunning, Feedback::State::kFeedbackCtrlRunning AddEvent("STOP") (bind(&StateMachineFeedback::StopFeedback, this)) ("Stop any control loop"); AddEvent("ENABLE_OUTPUT", "B:1")//, Feedback::State::kIdle) (bind(&StateMachineFeedback::EnableOutput, this, placeholders::_1)) ("Enable sending of correction values caluclated by the control loop to the biasctrl"); AddEvent("STORE_REFERENCE")//, Feedback::State::kIdle) (bind(&StateMachineFeedback::StoreReference, this)) ("Store the last (averaged) value as new reference (for debug purpose only)"); AddEvent("SET_REFERENCE", "F:1")//, Feedback::State::kIdle) (bind(&StateMachineFeedback::SetReference, this, placeholders::_1)) ("Set a new global reference value (for debug purpose only)"); AddEvent("SET_Ki", "D:1")//, Feedback::State::kIdle) (bind(&StateMachineFeedback::SetConstant, this, placeholders::_1, 0)) ("Set integral constant Ki"); AddEvent("SET_Kp", "D:1")//, Feedback::State::kIdle) (bind(&StateMachineFeedback::SetConstant, this, placeholders::_1, 1)) ("Set proportional constant Kp"); AddEvent("SET_Kd", "D:1")//, Feedback::State::kIdle) (bind(&StateMachineFeedback::SetConstant, this, placeholders::_1, 2)) ("Set derivative constant Kd"); AddEvent("SET_T", "D:1")//, Feedback::State::kIdle) (bind(&StateMachineFeedback::SetConstant, this, placeholders::_1, 3)) ("Set time-constant. (-1 to use the cycle time, i.e. the time for the last average cycle, instead)"); AddEvent("CALIBRATE_CURRENTS", Feedback::State::kConnectedFSC, Feedback::State::kConnected)//, Feedback::State::kIdle) (bind(&StateMachineFeedback::CalibrateCurrents, this)) (""); AddEvent("SET_CURRENT_REQUEST_INTERVAL", Feedback::State::kConnectedFSC, Feedback::State::kConnected)//, Feedback::State::kIdle) (bind(&StateMachineFeedback::SetCurrentRequestInterval, this, placeholders::_1)) ("|interval[ms]:Interval between two current requests in modes which need that."); // Verbosity commands // AddEvent("SET_VERBOSE", "B:1") // (bind(&StateMachineMCP::SetVerbosity, this, placeholders::_1)) // ("set verbosity state" // "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data"); AddEvent("PRINT") (bind(&StateMachineFeedback::Print, this)) (""); AddEvent("PRINT_CALIBRATION") (bind(&StateMachineFeedback::PrintCalibration, this)) (""); } int EvalOptions(Configuration &conf) { if (!fMap.Read(conf.Get("pixel-map-file"))) { Error("Reading mapping table from "+conf.Get("pixel-map-file")+" failed."); return 1; } fGain = 0.1; // V(Amplitude) / V(Bias) // 148 -> 248 // 33 : 10s < 2% // 50 : 5s < 2% // 66 : 3s < 2% // 85 : 2s < 2% fKp = 0; fKd = 0; fKi = 0.75; fT = 1; // Is that independent of the aboslute real amplitude of // the light pulser? ostringstream msg; msg << "Control loop parameters: "; msg << "Kp=" << fKp << ", Kd=" << fKd << ", Ki=" << fKi << ", "; if (fT>0) msg << fT; else msg << ""; msg << ", Gain(DRS/BIAS)=" << fGain << "V/V"; Message(msg); fCurrentRequestInterval = conf.Get("current-request-interval"); fNumCalibIgnore = conf.Get("num-calib-ignore"); fNumCalibRequests = conf.Get("num-calib-average"); fCalibrationOffset = conf.Get("calibration-offset"); return -1; } }; // ------------------------------------------------------------------------ #include "Main.h" template int RunShell(Configuration &conf) { return Main::execute(conf); } void SetupConfiguration(Configuration &conf) { po::options_description control("Feedback options"); control.add_options() ("pixel-map-file", var()->required(), "Pixel mapping file. Used here to get the default reference voltage.") ("current-request-interval", var(1000), "Interval between two current requests.") ("num-calib-ignore", var(30), "Number of current requests to be ignored before averaging") ("num-calib-average", var(300), "Number of current requests to be averaged") ("calibration-offset", var(-3), "Absolute offset relative to the G-APD operation voltage when calibrating") ; conf.AddOptions(control); } /* Extract usage clause(s) [if any] for SYNOPSIS. Translators: "Usage" and "or" here are patterns (regular expressions) which are used to match the usage synopsis in program output. An example from cp (GNU coreutils) which contains both strings: Usage: cp [OPTION]... [-T] SOURCE DEST or: cp [OPTION]... SOURCE... DIRECTORY or: cp [OPTION]... -t DIRECTORY SOURCE... */ void PrintUsage() { cout << "The feedback control the BIAS voltages based on the calibration signal.\n" "\n" "The default is that the program is started without user intercation. " "All actions are supposed to arrive as DimCommands. Using the -c " "option, a local shell can be initialized. With h or help a short " "help message about the usuage can be brought to the screen.\n" "\n" "Usage: feedback [-c type] [OPTIONS]\n" " or: feedback [OPTIONS]\n"; cout << endl; } void PrintHelp() { Main::PrintHelp(); /* Additional help text which is printed after the configuration options goes here */ /* cout << "bla bla bla" << endl << endl; cout << endl; cout << "Environment:" << endl; cout << "environment" << endl; cout << endl; cout << "Examples:" << endl; cout << "test exam" << endl; cout << endl; cout << "Files:" << endl; cout << "files" << endl; cout << endl; */ } int main(int argc, const char* argv[]) { Configuration conf(argv[0]); conf.SetPrintUsage(PrintUsage); Main::SetupConfiguration(conf); SetupConfiguration(conf); if (!conf.DoParse(argc, argv, PrintHelp)) return 127; //try { // No console access at all if (!conf.Has("console")) { // if (conf.Get("no-dim")) // return RunShell(conf); // else return RunShell(conf); } // Cosole access w/ and w/o Dim /* if (conf.Get("no-dim")) { if (conf.Get("console")==0) return RunShell(conf); else return RunShell(conf); } else */ { if (conf.Get("console")==0) return RunShell(conf); else return RunShell(conf); } } /*catch (std::exception& e) { cerr << "Exception: " << e.what() << endl; return -1; }*/ return 0; }