1 /**
  2  * @fileOverview This file has functions related to documenting JavaScript.
  3  * @author <a href="mailto:thomas.bretz@epfl.ch">Thomas Bretz</a>
  4  */
  5 'use strict';
  6 
  7 dim.log("Start: "+__FILE__+" ["+__DATE__+"]");
  8 
  9 // This should be set in dimctrl.rc as JavaScript.schedule-database.
 10 // It is sent together with the script to the dimserver.
 11 // If started directly, it has to be set after the command:
 12 //
 13 //   .js scripts/Main.js schedule-database=...
 14 //
 15 if (!$['schedule-database'])
 16     throw new Error("Environment 'schedule-database' not set!");
 17 
 18 //dimctrl.defineState(37, "TimeOutBeforeTakingData", "MCP took more than 5minutes to start TakingData");
 19 
 20 // ================================================================
 21 //  Code related to the schedule
 22 // ================================================================
 23 
 24 //this is just the class implementation of 'Observation'
 25 include('scripts/Observation_class.js');
 26 include('scripts/getSchedule.js');
 27 
 28 var observations = [ ];
 29 
 30 // Get the observation scheduled for 'now' from the table and
 31 // return its index
 32 function getObservation(now)
 33 {
 34     if (now==undefined)
 35         now = new Date();
 36 
 37     if (isNaN(now.valueOf()))
 38         throw new Error("Date argument in getObservation invalid.");
 39 
 40     observations = getSchedule();
 41 
 42     for (var i=0; i<observations.length; i++)
 43         if (now<observations[i].start)
 44             return i-1;
 45 
 46     return observations.length-1;
 47 }
 48 
 49 // ================================================================
 50 //  Code to check whether observation is allowed
 51 // ================================================================
 52 /*
 53 function currentEst(source)
 54 {
 55     var moon = new Moon();
 56     if (!moon.isUp)
 57         return 7.7;
 58 
 59     var dist = Sky.dist(moon, source);
 60 
 61     var alt = 90-moon.toLocal().zd;
 62 
 63     var lc = dist*alt*pow(Moon.disk(), 6)/360/360;
 64 
 65     var cur = 7.7+4942*lc;
 66 
 67     return cur;
 68 }
 69 
 70 function thresholdEst(source) // relative threshold (ratio)
 71 {
 72     // Assumption:
 73     // atmosphere is 70km, shower taks place after 60km, earth radius 6400km
 74     // just using the cosine law
 75     // This fits very well with MC results: See Roger Firpo, p.45
 76     // "Study of the MAGIC telescope sensitivity for Large Zenith Angle observations"
 77 
 78     var c = Math.cos(Math.Pi-source.zd);
 79     var ratio = (10*sqrt(409600*c*c+9009) + 6400*c - 60)/10;
 80 
 81     // assumption: Energy threshold increases linearily with current
 82     // assumption: Energy threshold increases linearily with distance
 83 
 84     return ratio*currentEst(source)/7.7;
 85 }
 86 */
 87 
 88 // ================================================================
 89 //  Code to perform the DRS calib sequence
 90 // ================================================================
 91 
 92 var irq;
 93 
 94 function doDrsCalibration(where)
 95 {
 96     dim.log("Starting DRS calibration ["+where+"]");
 97 
 98     service_feedback.voltageOff();
 99 
100     var tm = new Date();
101 
102     while (!irq)
103     {
104         dim.send("FAD_CONTROL/START_DRS_CALIBRATION");
105         if (irq || !takeRun("drs-pedestal", 1000))     // 40 / 20s     (50Hz)
106             continue;
107 
108         if (irq || !takeRun("drs-gain",     1000))     // 40 / 20s     (50Hz)
109             continue;
110 
111         if (where!="data")
112         {
113             if (irq || !takeRun("drs-pedestal", 1000))     // 40 / 20s     (50Hz)
114                 continue;
115         }
116 
117         break;
118     }
119 
120     if (where!="data")
121     {
122         dim.send("FAD_CONTROL/SET_FILE_FORMAT", 6);
123 
124         while (!irq && !takeRun("drs-pedestal", 1000));     // 40 / 20s     (50Hz)
125         while (!irq && !takeRun("drs-time",     1000));     // 40 / 20s     (50Hz)
126     }
127 
128     while (!irq)
129     {
130         dim.send("FAD_CONTROL/RESET_SECONDARY_DRS_BASELINE");
131         if (takeRun("pedestal",     1000))              // 40 / 10s     (80Hz)
132             break;
133     }
134 
135     dim.send("FAD_CONTROL/SET_FILE_FORMAT", 6);
136 
137     while (!irq && !takeRun("pedestal",     1000));     // 40 / 10s     (80Hz)
138     //                                                   -----------
139     //                                                   4'40 / 2'00
140 
141     if (irq)
142         dim.log("DRS calibration interrupted [%.1fs]".$((new Date()-tm)/1000));
143     else
144         dim.log("DRS calibration done [%.1fs]".$((new Date()-tm)/1000));
145 }
146 
147 // ================================================================
148 //  Code related to the lid
149 // ================================================================
150 
151 function OpenLid()
152 {
153     /*
154     while (Sun.horizon(-13).isUp)
155     {
156         var now = new Date();
157         var minutes_until_sunset = (Sun.horizon(-13).set - now)/60000;
158         console.out(now.toUTCString()+": Sun above FACT-horizon, lid cannot be opened: sleeping 1min, remaining %.1fmin".$(minutes_until_sunset));
159         v8.sleep(60000);
160     }*/
161 
162     var isClosed = dim.state("LID_CONTROL").name=="Closed";
163     var isInconsistent = dim.state("LID_CONTROL").name=="Inconsistent";
164 
165     var tm = new Date();
166 
167     // Wait for lid to be open
168     if (isClosed || isInconsistent)
169     {
170         dim.log("Opening lid");
171         dim.send("LID_CONTROL/OPEN");
172 
173         dim.log("Turning off IR camera LEDs...");
174 
175         var cam = new Curl("fact@cam/cgi-bin/user/Config.cgi");
176         cam.data.push("action=set");
177         cam.data.push("Camera.System.Title=Camera1");
178         cam.data.push("Camera.General.IRControl.Value=2");
179         cam.data.push("Camera.System.Display=ALL");
180         cam.data.push("Camera.Environment=OUTDOOR");
181         var ret = cam.send();
182         dim.log("Camera response: "+ret.data.replace("\n","/")+" ["+ret.rc+"]");
183     }
184     dim.wait("LID_CONTROL", "Open", 30000);
185 
186     if (isClosed || isInconsistent)
187         dim.log("Lid open [%.1fs]".$((new Date()-tm)/1000));
188 }
189 
190 function CloseLid()
191 {
192     var isOpen = dim.state("LID_CONTROL").name=="Open";
193 
194     var tm = new Date();
195 
196     // Wait for lid to be open
197     if (isOpen)
198     {
199         if (dim.state("FTM_CONTROL").name=="TriggerOn")
200         {
201             dim.send("FTM_CONTROL/STOP_TRIGGER");
202             dim.wait("FTM_CONTROL", "Valid", 3000);
203         }
204 
205         dim.log("Closing lid.");
206         dim.send("LID_CONTROL/CLOSE");
207     }
208     v8.timeout(30000, function() { if (dim.state("LID_CONTROL").name=="Closed" || dim.state("LID_CONTROL").name=="Inconsistent") return true; });
209     //dim.wait("LID_CONTROL", "Closed", 30000);
210     //dim.wait("LID_CONTROL", "Inconsistent", 30000);
211 
212     if (isOpen)
213         dim.log("Lid closed [%.1fs]".$((new Date()-tm)/1000));
214 }
215 
216 // ================================================================
217 //  Interrupt data taking in case of high currents
218 // ================================================================
219 dim.onchange['FEEDBACK'] = function(state)
220 {
221     if ((state.name=="Critical" || state.name=="OnStandby") &&
222         (this.prev!="Critical"  && this.prev!="OnStandby"))
223     {
224         console.out("Feedback state changed from "+this.prev+" to "+state.name+" [Main.js]");
225         irq = "RESCHEDULE";
226     }
227     this.prev=state.name;
228 }
229 
230 // ================================================================
231 //  Code related to switching bias voltage on and off
232 // ================================================================
233 
234 var service_feedback = new Subscription("FEEDBACK/CALIBRATED_CURRENTS");
235 
236 service_feedback.onchange = function(evt)
237 {
238     if (!evt.data)
239         return;
240 
241     if (this.ok==undefined)
242         return;
243 
244     var Unom = evt.obj['U_nom'];
245     var Uov  = evt.obj['U_ov'];
246     if (!Uov)
247         return;
248 
249     var cnt = 0;
250     var avg = 0;
251     for (var i=0; i<320; i++)
252     {
253         // This is a fix for the channel with a shortcut
254         if (i==272)
255             continue;
256 
257         var dU = Uov[i]-Unom;
258 
259         // 0.022 corresponds to 1 DAC count (90V/4096)
260         if (Math.abs(dU)>0.033)
261             cnt++;
262 
263         avg += dU;
264     }
265     avg /= 320;
266 
267     this.ok = cnt<3;// || (this.last!=undefined && Math.abs(this.last-avg)<0.002);
268 
269     console.out("  DeltaUov=%.3f (%.3f) [N(>0.033V)=%d]".$(avg, avg-this.last, cnt));
270 
271     this.last = avg;
272 }
273 
274 service_feedback.voltageOff = function()
275 {
276     var state = dim.state("BIAS_CONTROL").name;
277 
278     if (state=="Disconnected")
279     {
280         console.out("  Voltage off: bias crate disconnected!");
281         return;
282     }
283 
284     // check of feedback has to be switched on
285     var isOn = state=="VoltageOn" || state=="Ramping";
286     if (isOn)
287     {
288         dim.log("Switching voltage off.");
289 
290         if (dim.state("FTM_CONTROL").name=="TriggerOn")
291         {
292             dim.send("FTM_CONTROL/STOP_TRIGGER");
293             dim.wait("FTM_CONTROL", "Valid", 3000);
294         }
295 
296         // Supress the possibility that the bias control is
297         // ramping and will reject the command to switch the
298         // voltage off
299         //dim.send("FEEDBACK/STOP");
300         //dim.wait("FEEDBACK", "Calibrated", 3000);
301 
302         // Make sure we are not in Ramping anymore
303         //dim.wait("BIAS_CONTROL", "VoltageOn", 3000);
304 
305         // Switch voltage off
306         dim.send("BIAS_CONTROL/SET_ZERO_VOLTAGE");
307     }
308 
309     dim.wait("BIAS_CONTROL", "VoltageOff", 60000); // FIXME: 30000?
310     dim.wait("FEEDBACK",     "Calibrated",  3000);
311 
312     // FEEDBACK stays in CurrentCtrl when Voltage is off but output enabled
313     // dim.wait("FEEDBACK", "CurrentCtrlIdle", 1000);
314 
315     if (isOn)
316         dim.log("Voltage off.");
317 }
318 
319 // DN:  The name of the method voltageOn() in the context of the method
320 //      voltageOff() is a little bit misleading, since when voltageOff() returns
321 //      the caller can be sure the voltage is off, but when voltageOn() return
322 //      this is not the case, in the sense, that the caller can now take data.
323 //      instead the caller of voltageOn() *must* call waitForVoltageOn() afterwards
324 //      in order to safely take good-quality data.
325 //      This could lead to nasty bugs in the sense, that the second call might 
326 //      be forgotten by somebody
327 //      
328 //      so I suggest to rename voltageOn() --> prepareVoltageOn()
329 //      waitForVoltageOn() stays as it is
330 //      and one creates a third method called:voltageOn() like this
331 /*      service_feedback.voltageOn = function()
332  *      {
333  *          this.prepareVoltageOn();
334  *          this.waitForVoltageOn();
335  *      }
336  * 
337  * */
338 //      For convenience.
339 
340 service_feedback.voltageOn = function(ov)
341 {
342     if (isNaN(ov))
343         ov = 1.1;
344 
345     if (this.ov!=ov && dim.state("FEEDBACK").name=="InProgress") // FIXME: Warning, OnStandby, Critical if (ov<this.ov)
346     {
347         dim.log("Stoping feedback.");
348         if (dim.state("FTM_CONTROL").name=="TriggerOn")
349         {
350             dim.send("FTM_CONTROL/STOP_TRIGGER");
351             dim.wait("FTM_CONTROL", "Valid", 3000);
352         }
353 
354         dim.send("FEEDBACK/STOP");
355         dim.wait("FEEDBACK", "Calibrated", 3000);
356 
357         // Make sure we are not in Ramping anymore
358         dim.wait("BIAS_CONTROL", "VoltageOn", 3000);
359     }
360 
361     var isOff = dim.state("FEEDBACK").name=="Calibrated";
362     if (isOff)
363     {
364         dim.log("Switching voltage to Uov="+ov+"V.");
365 
366         dim.send("FEEDBACK/START", ov);
367 
368         // FIXME: We could miss "InProgress" if it immediately changes to "Warning"
369         //        Maybe a dim.timeout state>8 ?
370         dim.wait("FEEDBACK", "InProgress", 45000);
371 
372         this.ov = ov;
373     }
374 
375     // Wait until voltage on
376     dim.wait("BIAS_CONTROL", "VoltageOn", 60000); // FIXME: 30000?
377 }
378 
379 service_feedback.waitForVoltageOn = function()
380 {
381     // Avoid output if condition is already fulfilled
382     dim.log("Waiting for voltage to be stable.");
383 
384     function func()
385     {
386         if (irq || this.ok==true)
387             return true;
388     }
389 
390     var now = new Date();
391 
392     this.last = undefined;
393     this.ok = false;
394     v8.timeout(4*60000, func, this); // FIMXE: Remove 4!
395     this.ok = undefined;
396 
397     if (irq)
398         dim.log("Waiting for stable voltage interrupted.");
399     else
400         dim.log("Voltage stable within limits");
401 }
402 
403 // ================================================================
404 //  Function to shutdown the system
405 // ================================================================
406 
407 function Shutdown(type)
408 {
409     if (!type)
410         type = "default";
411 
412     dim.log("Starting shutdown ["+type+"].");
413 
414     var now1 = new Date();
415 
416     var bias = dim.state("BIAS_CONTROL").name;
417     if (bias=="VoltageOn" || bias=="Ramping")
418         service_feedback.voltageOn(0);
419 
420     CloseLid();
421 
422     var now2 = new Date();
423 
424     dim.send("DRIVE_CONTROL/PARK");
425 
426     console.out("","Waiting for telescope to park. This may take a while.");
427 
428     // FIXME: This might not work is the drive is already close to park position
429     //dim.wait("DRIVE_CONTROL", "Parking", 3000);
430 
431     /*
432     // Check if DRS calibration is necessary
433     var diff = getTimeSinceLastDrsCalib();
434     if (diff>30 || diff==null)
435     {
436         doDrsCalibration("singlepe");  // will turn voltage off
437         if (irq)
438             break;
439     }*/
440 
441     //take single pe run if required
442     if (type=="singlepe")
443     {
444         dim.log("Taking single-pe run.");
445 
446         // The voltage must be on
447         service_feedback.voltageOn();
448         service_feedback.waitForVoltageOn();
449 
450         // Before we can switch to 3000 we have to make the right DRS calibration
451         dim.log("Taking single p.e. run.");
452         while (!irq && !takeRun("single-pe", 10000));
453 
454         /*
455          Maybe we need to send a trigger... but data runs contain pedestal triggers... so it should work in any case...
456         var customRun = function()
457             {
458                 v8.sleep(500);//wait that configuration is set
459                 dim.wait("FTM_CONTROL", "TriggerOn", 15000);
460                 dim.send("FAD_CONTROL/SEND_SINGLE_TRIGGER");
461                 dim.send("RATE_CONTROL/STOP");
462                 dim.send("FTM_CONTROL/STOP_TRIGGER");
463                 dim.wait("FTM_CONTROL", "Valid", 3000);
464                 dim.send("FTM_CONTROL/ENABLE_TRIGGER", true);
465                 dim.send("FTM_CONTROL/SET_TIME_MARKER_DELAY", 123);
466                 dim.send("FTM_CONTROL/SET_THRESHOLD", -1, obs[sub].threshold);
467                 v8.sleep(500);//wait that configuration is set
468                 dim.send("FTM_CONTROL/START_TRIGGER");
469                 dim.wait("FTM_CONTROL", "TriggerOn", 15000);
470             }*/
471     }
472 
473     //wait until drive is in locked (after it reached park position)
474     dim.wait("DRIVE_CONTROL", "Locked", 150000);
475 
476     //unlock drive if task was sleep
477     if (type=="sleep")
478         dim.send("DRIVE_CONTROL/UNLOCK");
479 
480 
481     // It is unclear what comes next, so we better switch off the voltage
482     service_feedback.voltageOff();
483 
484     dim.log("Finishing shutdown.");
485 
486     var now3 = new Date();
487 
488     dim.send("FTM_CONTROL/STOP_TRIGGER");
489     dim.wait("FTM_CONTROL",  "Valid",        3000);
490 
491     if (bias!="Disconnected")
492         dim.wait("FEEDBACK", "Calibrated",   3000);
493 
494     if (type!="sleep")
495     {
496         dim.send("BIAS_CONTROL/DISCONNECT");
497 
498         var pwrctrl_state = dim.state("PWR_CONTROL").name;
499         if (pwrctrl_state=="SystemOn" ||
500             pwrctrl_state=="BiasOff"  ||
501             pwrctrl_state=="DriveOn")
502             dim.send("PWR_CONTROL/TOGGLE_DRIVE");
503 
504         dim.wait("BIAS_CONTROL", "Disconnected", 3000);
505         dim.wait("PWR_CONTROL",  "DriveOff",     6000);
506     }
507 
508     var sub = new Subscription("DRIVE_CONTROL/POINTING_POSITION");
509     sub.get(5000);  // FIXME: Proper error message in case of failure
510 
511     var report = sub.get();
512 
513     console.out("");
514     console.out("Shutdown procedure ["+type+"] seems to be finished...");
515     console.out("  "+new Date().toUTCString());
516     console.out("  Telescope at Zd=%.1fdeg Az=%.1fdeg".$(report.obj['Zd'], report.obj['Az']));
517     console.out("  Please check on the web cam that the park position was reached");
518     console.out("  and the telescope is not moving anymore.");
519     console.out("  Please check visually that the lid is really closed and");
520     console.out("  that the biasctrl really switched the voltage off.", "");
521     console.out("    DRIVE_CONTROL: "+dim.state("DRIVE_CONTROL").name);
522     console.out("    FEEDBACK:      "+dim.state("FEEDBACK").name);
523     console.out("    FTM_CONTROL:   "+dim.state("FTM_CONTROL").name);
524     console.out("    BIAS_CONTROL:  "+dim.state("BIAS_CONTROL").name);
525     console.out("    PWR_CONTROL:   "+dim.state("PWR_CONTROL").name);
526     console.out("");
527     dim.log("Shutdown: end ["+(now2-now1)/1000+"s, "+(now3-now2)/1000+"s, "+(new Date()-now3)/1000+"s]");
528     console.out("");
529 
530     sub.close();
531 }
532 
533 
534 // ================================================================
535 //  Function to set the system to sleep-mode
536 // ================================================================
537 // FIXME: do not repeat code from shutdown-function
538 /*
539 function GoToSleep()
540 {
541     CloseLid();
542 
543     var isArmed = dim.state("DRIVE_CONTROL").name=="Armed";
544     if (!isArmed)
545     {
546         dim.log("Drive not ready to move. -> send STOP");
547         dim.send("DRIVE_CONTROL/STOP");
548         dim.wait("DRIVE_CONTROL", "Armed", 5000);
549     }
550 
551     dim.send("DRIVE_CONTROL/MOVE_TO 101 0");//park position
552     var sub = new Subscription("DRIVE_CONTROL/POINTING_POSITION");
553     sub.get(5000);  // FIXME: Proper error message in case of failure
554 
555     function func()
556     {
557         var report = sub.get();
558 
559         var zd = report.obj['Zd'];
560         var az = report.obj['Az'];
561 
562         if (zd>100 && Math.abs(az)<1)
563             return true;
564 
565         return undefined;
566     }
567 
568     try { v8.timeout(150000, func); }
569     catch (e)
570     {
571         var p = sub.get();
572         dim.log('Park position not reached? Telescope at Zd='+p.obj['Zd']+' Az='+p.obj['Az']);
573     }
574     var p2 = sub.get();
575     dim.log('Telescope at Zd=%.1fdeg Az=%.1fdeg'.$(p2.obj['Zd'], p2.obj['Az']));
576     sub.close();
577 }
578 */
579 
580 // ================================================================
581 // Check datalogger subscriptions
582 // ================================================================
583 
584 var datalogger_subscriptions = new Subscription("DATA_LOGGER/SUBSCRIPTIONS");
585 datalogger_subscriptions.get(3000, false);
586 
587 datalogger_subscriptions.check = function()
588 {
589     var obj = this.get();
590     if (!obj.data)
591         throw new Error("DATA_LOGGER/SUBSCRIPTIONS not available.");
592 
593     var expected =
594         [
595          "AGILENT_CONTROL_24V/DATA",
596          "AGILENT_CONTROL_50V/DATA",
597          "AGILENT_CONTROL_80V/DATA",
598          "BIAS_CONTROL/CURRENT",
599          "BIAS_CONTROL/DAC",
600          "BIAS_CONTROL/NOMINAL",
601          "BIAS_CONTROL/VOLTAGE",
602          "DRIVE_CONTROL/POINTING_POSITION",
603          "DRIVE_CONTROL/SOURCE_POSITION",
604          "DRIVE_CONTROL/STATUS",
605          "DRIVE_CONTROL/TRACKING_POSITION",
606          "FAD_CONTROL/CONNECTIONS",
607          "FAD_CONTROL/DAC",
608          "FAD_CONTROL/DNA",
609          "FAD_CONTROL/DRS_RUNS",
610          "FAD_CONTROL/EVENTS",
611          "FAD_CONTROL/FEEDBACK_DATA",
612          "FAD_CONTROL/FILE_FORMAT",
613          "FAD_CONTROL/FIRMWARE_VERSION",
614          "FAD_CONTROL/INCOMPLETE",
615          "FAD_CONTROL/PRESCALER",
616          "FAD_CONTROL/REFERENCE_CLOCK",
617          "FAD_CONTROL/REGION_OF_INTEREST",
618          "FAD_CONTROL/RUNS",
619          "FAD_CONTROL/RUN_NUMBER",
620          "FAD_CONTROL/START_RUN",
621          "FAD_CONTROL/STATISTICS1",
622          "FAD_CONTROL/STATS",
623          "FAD_CONTROL/STATUS",
624          "FAD_CONTROL/TEMPERATURE",
625          "FEEDBACK/CALIBRATED_CURRENTS",
626          "FEEDBACK/CALIBRATION",
627          "FEEDBACK/CALIBRATION_R8",
628          "FEEDBACK/CALIBRATION_STEPS",
629 /*         "FEEDBACK/REFERENCE",*/
630          "FSC_CONTROL/CURRENT",
631          "FSC_CONTROL/HUMIDITY",
632          "FSC_CONTROL/TEMPERATURE",
633          "FSC_CONTROL/VOLTAGE",
634          "FTM_CONTROL/COUNTER",
635          "FTM_CONTROL/DYNAMIC_DATA",
636          "FTM_CONTROL/ERROR",
637          "FTM_CONTROL/FTU_LIST",
638          "FTM_CONTROL/PASSPORT",
639          "FTM_CONTROL/STATIC_DATA",
640          "FTM_CONTROL/TRIGGER_RATES",
641          "GPS_CONTROL/NEMA",
642          "SQM_CONTROL/DATA",
643          "LID_CONTROL/DATA",
644          "MAGIC_LIDAR/DATA",
645          "MAGIC_WEATHER/DATA",
646          "MCP/CONFIGURATION",
647          "PWR_CONTROL/DATA",
648          "RATE_CONTROL/THRESHOLD",
649          "RATE_SCAN/DATA",
650          "RATE_SCAN/PROCESS_DATA",
651          "TEMPERATURE/DATA",
652          "TIME_CHECK/OFFSET",
653          "TNG_WEATHER/DATA",
654          "TNG_WEATHER/DUST",
655          "PFMINI_CONTROL/DATA",
656         ];
657 
658     function map(entry)
659     {
660         if (entry.length==0)
661             return undefined;
662 
663         var rc = entry.split(',');
664         if (rc.length!=2)
665             throw new Error("Subscription list entry '"+entry+"' has wrong number of elements.");
666         return rc;
667     }
668 
669     var list = obj.data.split('\n').map(map);
670     function check(name)
671     {
672         if (list.every(function(el){return el==undefined || el[0]!=name;}))
673             throw new Error("Subscription to '"+name+"' not available.");
674     }
675 
676     expected.forEach(check);
677 }
678 
679 
680 
681 // ================================================================
682 // Crosscheck all states
683 // ================================================================
684 
685 // ----------------------------------------------------------------
686 // Do a standard startup to bring the system in into a well
687 // defined state
688 // ----------------------------------------------------------------
689 include('scripts/Startup.js');
690 
691 // ================================================================
692 //  Code to monitor clock conditioner
693 // ================================================================
694 
695 var sub_counter = new Subscription("FTM_CONTROL/COUNTER");
696 sub_counter.onchange = function(evt)
697 {
698     if (evt.qos>0 && evt.qos!=2 && evt.qos&0x100==0)
699         throw new Error("FTM reports: clock conditioner not locked.");
700 }
701 
702 // ================================================================
703 //  Code related to monitoring the fad system
704 // ================================================================
705 
706 // This code is here, because scripts/Startup.js needs the
707 // same subscriptions... to be revised.
708 var sub_incomplete = new Subscription("FAD_CONTROL/INCOMPLETE");
709 var sub_connections = new Subscription("FAD_CONTROL/CONNECTIONS");
710 var sub_startrun = new Subscription("FAD_CONTROL/START_RUN");
711 sub_startrun.get(5000);
712 
713 include('scripts/takeRun.js');
714 
715 // ----------------------------------------------------------------
716 // Check that everything we need is availabel to receive commands
717 // (FIXME: Should that go to the general CheckState?)
718 // ----------------------------------------------------------------
719 //console.out("Checking send.");
720 checkSend(["MCP", "DRIVE_CONTROL", "LID_CONTROL", "FAD_CONTROL", "FEEDBACK"]);
721 //console.out("Checking send: done");
722 
723 // ----------------------------------------------------------------
724 // Bring feedback into the correct operational state
725 // ----------------------------------------------------------------
726 //console.out("Feedback init: start.");
727 service_feedback.get(5000);
728 
729 // ----------------------------------------------------------------
730 // Connect to the DRS_RUNS service
731 // ----------------------------------------------------------------
732 //console.out("Drs runs init: start.");
733 
734 var sub_drsruns = new Subscription("FAD_CONTROL/DRS_RUNS");
735 sub_drsruns.get(5000);
736 // FIXME: Check if the last DRS calibration was complete?
737 
738 function getTimeSinceLastDrsCalib()
739 {
740     // ----- Time since last DRS Calibration [min] ------
741     var runs = sub_drsruns.get(0);
742     var diff = (new Date()-runs.time)/60000;
743 
744     // Warning: 'roi=300' is a number which is not intrisically fixed
745     //          but can change depending on the taste of the observers
746     var valid = runs.obj['run'][2]>0 && runs.obj['roi']==300;
747 
748     if (valid)
749         dim.log("Last DRS calibration was %.1fmin ago".$(diff));
750     else
751         dim.log("No valid DRS calibration available.");
752 
753     return valid ? diff : null;
754 }
755 
756 // ----------------------------------------------------------------
757 // Install interrupt handler
758 // ----------------------------------------------------------------
759 function handleIrq(cmd, args, time, user)
760 {
761     console.out("Interrupt received:");
762     console.out("  IRQ:  "+cmd);
763     console.out("  Time: "+time);
764     console.out("  User: "+user);
765 
766     irq = cmd ? cmd : "stop";
767 
768     // This will end a run in progress as if it where correctly stopped
769     if (dim.state("MCP").name=="TakingData")
770         dim.send("MCP/STOP");
771 
772     // This will stop a rate scan in progress
773     if (dim.state("RATE_SCAN").name=="InProgress")
774         dim.send("RATE_SCAN/STOP");
775 }
776 
777 dimctrl.setInterruptHandler(handleIrq);
778 
779 // ----------------------------------------------------------------
780 // Make sure we will write files
781 // ----------------------------------------------------------------
782 dim.send("FAD_CONTROL/SET_FILE_FORMAT", 6);
783 
784 // ----------------------------------------------------------------
785 // Print some information for the user about the
786 // expected first oberservation
787 // ----------------------------------------------------------------
788 var test = getObservation();
789 if (test!=undefined)
790 {
791     var n = new Date();
792     if (observations.length>0 && test==-1)
793         dim.log("First observation scheduled for "+observations[0].start.toUTCString()+" [id="+observations[0].id+"]");
794     if (test>=0 && test<observations.length)
795         dim.log("First observation should start immediately ["+observations[test].start.toUTCString()+", id="+observations[test].id+"]");
796     if (observations.length>0 && observations[0].start>n+12*3600*1000)
797         dim.log("No observations scheduled for the next 12 hours!");
798     if (observations.length==0)
799         dim.log("No observations scheduled!");
800 }
801 
802 // ----------------------------------------------------------------
803 // Start main loop
804 // ----------------------------------------------------------------
805 dim.log("Entering main loop.");
806 console.out("");
807 
808 var run = -2; // getObservation never called
809 var sub;
810 var lastId;
811 var nextId;
812 var sun = Sun.horizon(-12);
813 var system_on;  // undefined
814 
815 function processIrq()
816 {
817     if (!irq)
818         return false;
819 
820     if (irq.toUpperCase()=="RESCHEDULE")
821     {
822         irq = undefined;
823         return false;
824     }
825 
826     if (irq.toUpperCase()=="OFF")
827     {
828         service_feedback.voltageOff();
829         dim.send("FAD_CONTROL/CLOSE_OPEN_FILES");
830         return true;
831     }
832 
833     /*
834     if (irq.toUpperCase()=="STOP")
835     {
836         dim.send("FAD_CONTROL/CLOSE_OPEN_FILES");
837         dim.send("MCP/STOP");
838         return true;
839     }*/
840 
841     if (irq.toUpperCase()=="SHUTDOWN")
842     {
843         Shutdown();
844         return true;
845     }
846 
847     dim.log("IRQ "+irq+" unhandled... stopping script.");
848     return true;
849 }
850 
851 while (!processIrq())
852 {
853     // Check if observation position is still valid
854     // If source position has changed, set run=0
855     var idxObs = getObservation();
856     if (idxObs===undefined)
857         break;
858 
859     // we are still waiting for the first observation in the schedule
860     if (idxObs==-1)
861     {
862         // flag that the first observation will be in the future
863         run = -1; 
864         v8.sleep(1000);
865         continue;
866     }
867 
868     // Check if we have to take action do to sun-rise
869     var was_up = sun.isUp;
870     sun = Sun.horizon(-12);
871     if (!was_up && sun.isUp)
872     {
873         console.out("");
874         dim.log("Sun rise detected.... automatic shutdown initiated!");
875         // FIXME: State check?
876         Shutdown();
877         system_on = false;
878         continue;
879     }
880 
881     // Current and next observation target
882     var obs     = observations[idxObs];
883     var nextObs = observations[idxObs+1];
884 
885     // Check if observation target has changed
886     if (lastId!=obs.id) // !Object.isEqual(obs, nextObs)
887     {
888         dim.log("Starting new observation ["+obs.start.toUTCString()+", id="+obs.id+"]");
889 
890         // This is the first source, but we do not come from
891         // a scheduled 'START', so we have to check if the
892         // telescop is operational already
893         sub = 0;
894         if (run<0)
895         {
896             //Startup();   // -> Bias On/Off?, Lid open/closed?
897             //CloseLid();
898         }
899 
900         // The first observation had a start-time in the past...
901         // In this particular case start with the last entry
902         // in the list of measurements
903         if (run==-2)
904             sub = obs.length-1;
905 
906         run = 0;
907         lastId = obs.id;
908     }
909 
910     //dim.log("DEBUG: Next observation scheduled for "+nextObs.start.toUTCString()+" [id="+nextObs.id+"]");
911     if (nextObs && nextId!=nextObs.id)
912     {
913         dim.log("Next observation scheduled for "+nextObs.start.toUTCString()+" [id="+nextObs.id+"]");
914         console.out("");
915         nextId = nextObs.id;
916     }
917 
918     if (!nextObs && nextId)
919     {
920         dim.log("No further observation scheduled.");
921         console.out("");
922         nextId = undefined;
923     }
924 
925     //if (nextObs==undefined && obs[obs.length-1].task!="SHUTDOWN")
926     //    throw Error("Last scheduled measurement must be a shutdown.");
927 
928     // We are done with all measurement slots for this
929     // observation... wait for next observation
930     if (sub>=obs.length)
931     {
932         v8.sleep(1000);
933         continue;
934     }
935 
936     if (system_on===false && obs[sub].task!="STARTUP")
937     {
938         v8.sleep(1000);
939         continue;
940     }
941 
942     // Check if sun is still up... only DATA and */
943     if ((obs[sub].task=="DATA" || obs[sub].task=="RATESCAN" || obs[sub].task=="RATESCAN2" ) && sun.isUp)
944     {
945         var now = new Date();
946         var remaining = (sun.set - now)/60000;
947         console.out(now.toUTCString()+" - "+obs[sub].task+": Sun above FACT-horizon: sleeping 1min, remaining %.1fmin".$(remaining));
948         v8.sleep(60000);
949         continue;
950     }
951 
952 
953     if (obs[sub].task!="IDLE" && (obs[sub].task!="DATA" && run>0))
954         dim.log("New task ["+obs[sub]+"]");
955 
956     // FIXME: Maybe print a warning if Drive is on during day time!
957 
958     // It is not ideal that we allow the drive to be on during day time, but
959     // otherwise it is difficult to allow e.g. the STARTUP at the beginning of the night
960     var power_states = sun.isUp || !system_on ? [ "DriveOff", "SystemOn" ] : [ "SystemOn" ];
961     var drive_states = sun.isUp || !system_on ? undefined : [ "Initialized", "Tracking", "OnTrack" ];
962 
963     // A scheduled task was found, lets check if all servers are
964     // still only and in reasonable states. If this is not the case,
965     // something unexpected must have happend and the script is aborted.
966     //console.out("  Checking states [general]");
967     var table =
968         [
969          [ "TNG_WEATHER"   ],
970          [ "MAGIC_WEATHER" ],
971          [ "CHAT"          ],
972          [ "SMART_FACT"    ],
973          [ "TEMPERATURE"   ],
974          [ "DATA_LOGGER",         [ "NightlyFileOpen", "WaitForRun", "Logging" ] ],
975          [ "FSC_CONTROL",         [ "Connected"                ] ],
976          [ "MCP",                 [ "Idle"                     ] ],
977          [ "TIME_CHECK",          [ "Valid"                    ] ],
978          [ "PWR_CONTROL",         power_states/*[ "SystemOn"                 ]*/ ],
979          [ "AGILENT_CONTROL_24V", [ "VoltageOn"                ] ],
980          [ "AGILENT_CONTROL_50V", [ "VoltageOn"                ] ],
981          [ "AGILENT_CONTROL_80V", [ "VoltageOn"                ] ],
982          [ "BIAS_CONTROL",        [ "VoltageOff", "VoltageOn", "Ramping" ] ],
983          [ "FEEDBACK",            [ "Calibrated", "InProgress", "OnStandby", "Warning", "Critical" ] ],
984 //         [ "LID_CONTROL",         [ "Open", "Closed"           ] ],
985          [ "LID_CONTROL",         [ "Open", "Closed", "Inconsistent"  ] ],
986          [ "DRIVE_CONTROL",       drive_states/*[ "Armed", "Tracking", "OnTrack" ]*/ ],
987          [ "FTM_CONTROL",         [ "Valid", "TriggerOn"       ] ],
988          [ "FAD_CONTROL",         [ "Connected", "RunInProgress" ] ],
989          [ "RATE_SCAN",           [ "Connected"                ] ],
990          [ "RATE_CONTROL",        [ "Connected", "GlobalThresholdSet", "InProgress"  ] ],
991          [ "GPS_CONTROL",         [ "Locked"  ] ],
992          [ "SQM_CONTROL",         [ "Disconnected", "Connected", "Valid" ] ],
993          [ "PFMINI_CONTROL",      [ "Disconnected", "Connected", "Receiving" ] ],
994         ];
995 
996 
997     if (!checkStates(table))
998     {
999         throw new Error("Something unexpected has happened. One of the servers "+
1000                         "is in a state in which it should not be. Please,"+ 
1001                         "try to find out what happened...");
1002     }
1003 
1004     datalogger_subscriptions.check();                                         
1005                                                                                 
1006     // If this is an observation which needs the voltage to be swicthed on
1007     // skip that if the voltage is not stable                                    
1008     /*
1009     if (obs[sub].task=="DATA" || obs[sub].task=="RATESCAN")
1010     {
1011         var state = dim.state("FEEDBACK").name;
1012         if (state=="Warning" || state=="Critical" || state=="OnStandby")
1013         {
1014             v8.sleep(1000);
1015             continue;
1016         }
1017     }*/
1018 
1019 
1020     // Check if obs.task is one of the one-time-tasks
1021     switch (obs[sub].task)
1022     {
1023     case "IDLE":
1024         v8.sleep(5000);
1025         continue;
1026 
1027     case "SLEEP":
1028         Shutdown("sleep"); //GoToSleep();
1029 
1030         sub++;
1031         dim.log("Task finished [SLEEP].");
1032         console.out("");
1033         continue;
1034 
1035     case "STARTUP":
1036         CloseLid();
1037 
1038         doDrsCalibration("startup");  // will switch the voltage off
1039 
1040         if (irq)
1041             break;
1042 
1043         service_feedback.voltageOn();
1044         service_feedback.waitForVoltageOn();
1045 
1046         // Before we can switch to 3000 we have to make the right DRS calibration
1047         dim.log("Taking single p.e. run.");
1048         while (!irq && !takeRun("single-pe", 10000));
1049 
1050         // It is unclear what comes next, so we better switch off the voltage
1051         service_feedback.voltageOff();
1052 
1053         system_on = true;
1054         dim.log("Task finished [STARTUP]");
1055         console.out("");
1056         break;
1057 
1058     case "SHUTDOWN":
1059         Shutdown("singlepe");
1060         system_on = false;
1061 
1062         // FIXME: Avoid new observations after a shutdown until
1063         //        the next startup (set run back to -2?)
1064         sub++;
1065         dim.log("Task finished [SHUTDOWN]");
1066         console.out("");
1067         //console.out("  Waiting for next startup.", "");
1068         continue;
1069 
1070     case "DRSCALIB":
1071         doDrsCalibration("drscalib");  // will switch the voltage off
1072         dim.log("Task finished [DRSCALIB]");
1073         console.out("");
1074         break;
1075 
1076     case "SINGLEPE":
1077         // The lid must be closes
1078         CloseLid();
1079 
1080         // Check if DRS calibration is necessary
1081         var diff = getTimeSinceLastDrsCalib();
1082         if (diff>30 || diff==null)
1083         {
1084             doDrsCalibration("singlepe");  // will turn voltage off
1085             if (irq)
1086                 break;
1087         }
1088 
1089         // The voltage must be on
1090         service_feedback.voltageOn();
1091         service_feedback.waitForVoltageOn();
1092 
1093         // Before we can switch to 3000 we have to make the right DRS calibration
1094         dim.log("Taking single p.e. run.");
1095         while (!irq && !takeRun("single-pe", 10000));
1096 
1097         // It is unclear what comes next, so we better switch off the voltage
1098         service_feedback.voltageOff();
1099         dim.log("Task finished [SINGLE-PE]");
1100         console.out("");
1101         break;
1102 
1103     case "OVTEST":
1104         var locked = dim.state("DRIVE_CONTROL").name=="Locked";
1105         if (!locked)
1106             dim.send("DRIVE_CONTROL/PARK");
1107 
1108         dim.send("FEEDBACK/STOP");
1109 
1110         // The lid must be closed
1111         CloseLid();
1112 
1113         if (!locked)
1114         {
1115             //console.out("Waiting for telescope to park. This may take a while.");
1116             dim.wait("DRIVE_CONTROL", "Locked", 3000);
1117             dim.send("DRIVE_CONTROL/UNLOCK");
1118         }
1119 
1120         // Check if DRS calibration is necessary
1121         var diff = getTimeSinceLastDrsCalib();
1122         if (diff>30 || diff==null)
1123         {
1124             doDrsCalibration("ovtest");  // will turn voltage off
1125             if (irq)
1126                 break;
1127         }
1128 
1129         // The voltage must be on
1130         service_feedback.voltageOn(0.4);
1131         service_feedback.waitForVoltageOn();
1132 
1133         dim.log("Taking single p.e. run (0.4V)");
1134         while (!irq && !takeRun("single-pe", 10000));
1135 
1136         for (var i=5; i<18 && !irq; i++)
1137         {
1138             dim.send("FEEDBACK/STOP");
1139             dim.wait("FEEDBACK", "Calibrated", 3000);
1140             dim.wait("BIAS_CONTROL", "VoltageOn", 3000);
1141             dim.send("FEEDBACK/START", i*0.1);
1142             dim.wait("FEEDBACK", "InProgress", 45000);
1143             dim.wait("BIAS_CONTROL", "VoltageOn", 60000); // FIXME: 30000?
1144             service_feedback.waitForVoltageOn();
1145             dim.log("Taking single p.e. run ("+(i*0.1)+"V)");
1146             while (!irq && !takeRun("single-pe", 10000));
1147         }
1148 
1149         // It is unclear what comes next, so we better switch off the voltage
1150         service_feedback.voltageOff();
1151         dim.log("Task finished [OVTEST]");
1152         console.out("");
1153         break;
1154 
1155     case "RATESCAN":
1156         var tm1 = new Date();
1157 
1158         // This is a workaround to make sure that we really catch
1159         // the new OnTrack state later and not the old one
1160         dim.send("DRIVE_CONTROL/STOP");
1161         dim.wait("DRIVE_CONTROL", "Initialized", 15000);
1162 
1163         // The lid must be open
1164         OpenLid();
1165 
1166         // Switch the voltage to a reduced level (Ubd)
1167         service_feedback.voltageOn(0);
1168 
1169         if (obs[sub].source != null) // undefined != null -> false
1170         {
1171             dim.log("Pointing telescope to '"+obs[sub].source+"'.");
1172             dim.send("DRIVE_CONTROL/TRACK_ON", obs[sub].source);
1173         }
1174         else
1175         {
1176             dim.log("Pointing telescope to ra="+obs[sub].ra+" dec="+obs[sub].dec);
1177             dim.send("DRIVE_CONTROL/TRACK", obs[sub].ra, obs[sub].dec);
1178         }
1179 
1180         dim.wait("DRIVE_CONTROL", "OnTrack", 150000); // 110s for turning and 30s for stabilizing
1181 
1182         // Now tracking stable, switch voltage to nominal level and wait
1183         // for stability.
1184         service_feedback.voltageOn();
1185         service_feedback.waitForVoltageOn();
1186 
1187         if (!irq)
1188         {
1189             dim.log("Starting calibration.");
1190 
1191             // Calibration (2% of 20')
1192             while (!irq)
1193             {
1194                 if (irq || !takeRun("pedestal",         1000))  // 80 Hz  -> 10s
1195                     continue;
1196                 //if (irq || !takeRun("light-pulser-ext", 1000))  // 80 Hz  -> 10s
1197                 //    continue;
1198                 break;
1199             }
1200 
1201             var tm2 = new Date();
1202 
1203             dim.log("Starting ratescan.");
1204 
1205             //set reference to whole camera (in case it was changed)
1206             dim.send("RATE_SCAN/SET_REFERENCE_CAMERA");
1207             // Start rate scan
1208             dim.send("RATE_SCAN/START_THRESHOLD_SCAN", 50, 1000, -10, "default");
1209 
1210             // Lets wait if the ratescan really starts... this might take a few
1211             // seconds because RATE_SCAN configures the ftm and is waiting for
1212             // it to be configured.
1213             dim.wait("RATE_SCAN", "InProgress", 10000);
1214             dim.wait("RATE_SCAN", "Connected", 2700000);
1215 
1216             // Here one could implement a watchdog for the feedback as well, but what is the difference
1217             // whether finally one has to find out if the feedback was in the correct state
1218             // or the ratescan was interrupted?
1219 
1220             // this line is actually some kind of hack.
1221             // after the Ratescan, no data is written to disk. I don't know why, but it happens all the time
1222             // So I decided to put this line here as a kind of patchwork....
1223             //dim.send("FAD_CONTROL/SET_FILE_FORMAT", 6);
1224 
1225             dim.log("Ratescan done [%.1fs, %.1fs]".$((tm2-tm1)/1000, (new Date()-tm2)/1000));
1226         }
1227 
1228         dim.log("Task finished [RATESCAN]");
1229         console.out("");
1230         break; // case "RATESCAN"
1231 
1232     case "RATESCAN2":
1233         var tm1 = new Date();
1234 
1235         // This is a workaround to make sure that we really catch
1236         // the new OnTrack state later and not the old one
1237         dim.send("DRIVE_CONTROL/STOP");
1238         dim.wait("DRIVE_CONTROL", "Initialized", 15000);
1239 
1240         if (obs[sub].rstype=="dark-bias-off")
1241             service_feedback.voltageOff();
1242         else
1243         {
1244             // Switch the voltage to a reduced level (Ubd)
1245             var bias = dim.state("BIAS_CONTROL").name;
1246             if (bias=="VoltageOn" || bias=="Ramping")
1247                 service_feedback.voltageOn(0);
1248         }
1249 
1250         // Open the lid if required
1251         if (!obs[sub].lidclosed)
1252             OpenLid();
1253         else
1254             CloseLid();
1255 
1256         // track source/position or move to position
1257         if (obs[sub].lidclosed)
1258         {
1259             dim.log("Moving telescope to zd="+obs[sub].zd+" az="+obs[sub].az);
1260             dim.send("DRIVE_CONTROL/MOVE_TO", obs[sub].zd, obs[sub].az);
1261             v8.sleep(3000);
1262             dim.wait("DRIVE_CONTROL", "Initialized", 150000); // 110s for turning and 30s for stabilizing
1263         }
1264         else
1265         {
1266             if (obs[sub].source != null)  // undefined != null -> false
1267             {
1268                 dim.log("Pointing telescope to '"+obs[sub].source+"'.");
1269                 dim.send("DRIVE_CONTROL/TRACK_ON", obs[sub].source);
1270             }
1271             else
1272             {
1273                 dim.log("Pointing telescope to ra="+obs[sub].ra+" dec="+obs[sub].dec);
1274                 dim.send("DRIVE_CONTROL/TRACK", obs[sub].ra, obs[sub].dec);
1275             }
1276 
1277             dim.wait("DRIVE_CONTROL", "OnTrack", 150000); // 110s for turning and 30s for stabilizing
1278         }
1279 
1280         // Now tracking stable, switch voltage to nominal level and wait
1281         // for stability.
1282         if (obs[sub].rstype!="dark-bias-off")
1283         {
1284             service_feedback.voltageOn();
1285             service_feedback.waitForVoltageOn();
1286         }
1287 
1288         if (!irq)
1289         {
1290             var tm2 = new Date();
1291 
1292             dim.log("Starting ratescan 2/1 ["+obs[sub].rstype+"]");
1293 
1294             //set reference to whole camera (in case it was changed)
1295             dim.send("RATE_SCAN/SET_REFERENCE_CAMERA");
1296             // Start rate scan
1297             dim.send("RATE_SCAN/START_THRESHOLD_SCAN", 50, 300, 20, obs[sub].rstype);
1298 
1299             // Lets wait if the ratescan really starts... this might take a few
1300             // seconds because RATE_SCAN configures the ftm and is waiting for
1301             // it to be configured.
1302             dim.wait("RATE_SCAN", "InProgress", 10000);
1303             //FIXME: discuss what best value is here
1304             dim.wait("RATE_SCAN", "Connected", 2700000);//45min
1305             //dim.wait("RATE_SCAN", "Connected", 1200000);//3.3h
1306 
1307             // Here one could implement a watchdog for the feedback as well, but what is the difference
1308             // whether finally one has to find out if the feedback was in the correct state
1309             // or the ratescan was interrupted?
1310 
1311             // this line is actually some kind of hack.
1312             // after the Ratescan, no data is written to disk. I don't know why, but it happens all the time
1313             // So I decided to put this line here as a kind of patchwork....
1314             //dim.send("FAD_CONTROL/SET_FILE_FORMAT", 6);
1315 
1316             dim.log("Ratescan 2/1 done [%.1fs, %.1fs]".$((tm2-tm1)/1000, (new Date()-tm2)/1000));
1317         }
1318 
1319         if (!irq)
1320         {
1321             var tm2 = new Date();
1322 
1323             dim.log("Starting ratescan 2/2 ["+obs[sub].rstype+"]");
1324 
1325             // Start rate scan
1326             dim.send("RATE_SCAN/START_THRESHOLD_SCAN", 300, 1000, 100, obs[sub].rstype);
1327 
1328             // Lets wait if the ratescan really starts... this might take a few
1329             // seconds because RATE_SCAN configures the ftm and is waiting for
1330             // it to be configured.
1331             dim.wait("RATE_SCAN", "InProgress", 10000);
1332             dim.wait("RATE_SCAN", "Connected", 2700000);
1333 
1334             // Here one could implement a watchdog for the feedback as well, but what is the difference
1335             // whether finally one has to find out if the feedback was in the correct state
1336             // or the ratescan was interrupted?
1337 
1338             // this line is actually some kind of hack.
1339             // after the Ratescan, no data is written to disk. I don't know why, but it happens all the time
1340             // So I decided to put this line here as a kind of patchwork....
1341             //dim.send("FAD_CONTROL/SET_FILE_FORMAT", 6);
1342 
1343             dim.log("Ratescan 2/2 done [%.1fs, %.1fs]".$((tm2-tm1)/1000, (new Date()-tm2)/1000));
1344         }
1345 
1346         dim.log("Task finished [RATESCAN2]");
1347         console.out("");
1348         break; // case "RATESCAN2"
1349 
1350     case "CUSTOM":
1351 
1352         // This is a workaround to make sure that we really catch
1353         // the new OnTrack state later and not the old one
1354         dim.send("DRIVE_CONTROL/STOP");
1355         dim.wait("DRIVE_CONTROL", "Initialized", 15000);
1356 
1357         // Ramp bias if needed
1358         if (!obs[sub].biason)
1359             service_feedback.voltageOff();
1360         else
1361         {
1362             // Switch the voltage to a reduced level (Ubd)
1363             var bias = dim.state("BIAS_CONTROL").name;
1364             if (bias=="VoltageOn" || bias=="Ramping")
1365                 service_feedback.voltageOn(0);
1366         }
1367         // Close lid
1368         CloseLid();
1369 
1370         // Move to position (zd/az)
1371         dim.log("Moving telescope to zd="+obs[sub].zd+" az="+obs[sub].az);
1372         dim.send("DRIVE_CONTROL/MOVE_TO", obs[sub].zd, obs[sub].az);
1373         v8.sleep(3000);
1374         dim.wait("DRIVE_CONTROL", "Initialized", 150000); // 110s for turning and 30s for stabilizing
1375 
1376         // Now tracking stable, switch voltage to nominal level and wait
1377         // for stability.
1378         if (obs[sub].biason)
1379         {
1380             service_feedback.voltageOn();
1381             service_feedback.waitForVoltageOn();
1382         }
1383 
1384         if (!irq)
1385         {
1386             dim.log("Taking custom run with time "+obs[sub].time+"s, threshold="+obs[sub].threshold+", biason="+obs[sub].biason);
1387 
1388             var customRun = function()
1389             {
1390                 v8.sleep(500);//wait that configuration is set
1391                 dim.wait("FTM_CONTROL", "TriggerOn", 15000);
1392                 dim.send("FAD_CONTROL/SEND_SINGLE_TRIGGER");
1393                 dim.send("RATE_CONTROL/STOP");
1394                 dim.send("FTM_CONTROL/STOP_TRIGGER");
1395                 dim.wait("FTM_CONTROL", "Valid", 3000);
1396                 dim.send("FTM_CONTROL/ENABLE_TRIGGER", true);
1397                 dim.send("FTM_CONTROL/SET_TIME_MARKER_DELAY", 123);
1398                 dim.send("FTM_CONTROL/SET_THRESHOLD", -1, obs[sub].threshold);
1399                 v8.sleep(500);//wait that configuration is set
1400                 dim.send("FTM_CONTROL/START_TRIGGER");
1401                 dim.wait("FTM_CONTROL", "TriggerOn", 15000);
1402             }
1403 
1404             takeRun("custom", -1, obs[sub].time, customRun);
1405         }
1406         dim.log("Task finished [CUSTOM].");
1407         dim.log("");
1408         break; // case "CUSTOM"
1409 
1410     case "DATA":
1411 
1412         // ========================== case "DATA" ============================
1413     /*
1414         if (Sun.horizon("FACT").isUp)
1415         {
1416             console.out("  SHUTDOWN","");
1417             Shutdown();
1418             console.out("  Exit forced due to broken schedule", "");
1419             exit();
1420         }
1421     */
1422 
1423         // Calculate remaining time for this observation in minutes
1424         var remaining = nextObs==undefined ? 0 : (nextObs.start-new Date())/60000;
1425         //dim.log("DEBUG: remaining: "+remaining+" nextObs="+nextObs+" start="+nextObs.start);
1426 
1427         // ------------------------------------------------------------
1428 
1429         dim.log("Run count "+run+" [remaining "+parseInt(remaining)+"min]");
1430 
1431         // ----- Time since last DRS Calibration [min] ------
1432         var diff = getTimeSinceLastDrsCalib();
1433 
1434         // Changine pointing position and take calibration...
1435         //  ...every four runs (every ~20min)
1436         //  ...if at least ten minutes of observation time are left
1437         //  ...if this is the first run on the source
1438         var point  = (run%4==0 && remaining>10 && !obs[sub].orbit) || run==0; // undefined==null -> true!
1439 
1440         // Take DRS Calib...
1441         //  ...every four runs (every ~20min)
1442         //  ...at last  every two hours
1443         //  ...when DRS temperature has changed by more than 2deg (?)
1444         //  ...when more than 15min of observation are left
1445         //  ...no drs calibration was done yet
1446         var drscal = (run%4==0 && (remaining>15 && diff>70)) || diff==null;
1447     
1448         if (point)
1449         {
1450             // Switch the voltage to a reduced voltage level
1451             service_feedback.voltageOn(0);
1452 
1453             // Change wobble position every four runs,
1454             // start with alternating wobble positions each day
1455             var wobble = (parseInt(run/4) + parseInt(new Date()/1000/3600/24-0.5))%2+1;
1456             var angle  = obs[sub].angle == null ? Math.random()*360 : obs[sub].angle;
1457 
1458             if (obs[sub].orbit) // != undefined, != null, != 0
1459                 dim.log("Pointing telescope to '"+obs[sub].source+"' [orbit="+obs[sub].orbit+"min, angle="+angle+"]");
1460             else
1461                 dim.log("Pointing telescope to '"+obs[sub].source+"' [wobble="+wobble+"]");
1462 
1463             // This is a workaround to make sure that we really catch
1464             // the new OnTrack state later and not the old one
1465             dim.send("DRIVE_CONTROL/STOP");
1466             dim.wait("DRIVE_CONTROL", "Initialized", 15000);
1467 
1468             if (obs[sub].orbit) // != undefined, != null, != 0
1469                 dim.send("DRIVE_CONTROL/TRACK_ORBIT", angle, obs[sub].orbit, obs[sub].source);
1470             else
1471                 dim.send("DRIVE_CONTROL/TRACK_WOBBLE", wobble, obs[sub].source);
1472 
1473             // Do we have to check if the telescope is really moving?
1474             // We can cross-check the SOURCE service later
1475         }
1476 
1477         if (drscal)
1478         {
1479             doDrsCalibration("data");  // will turn voltage off
1480 
1481             // Now we switch on the voltage and a significant amount of
1482             // time has been passed, so do the check again.
1483             sun = Sun.horizon(-12);
1484             if (!was_up && sun.isUp)
1485             {
1486                 dim.log("Sun rise detected....");
1487                 continue;
1488             }
1489         }
1490 
1491         if (irq)
1492             continue;
1493 
1494         OpenLid();
1495 
1496         // This is now th right time to wait for th drive to be stable
1497         dim.wait("DRIVE_CONTROL", "OnTrack", 150000); // 110s for turning and 30s for stabilizing
1498 
1499         // Now check the voltage... (do not start a lot of stuff just to do nothing)
1500         var state = dim.state("FEEDBACK").name;
1501         if (state=="Warning" || state=="Critical" || state=="OnStandby")
1502         {
1503             v8.sleep(60000);
1504             continue;
1505         }
1506 
1507         // Now we are 'OnTrack', so we can ramp to nominal voltage
1508         // and wait for the feedback to get stable
1509         service_feedback.voltageOn();
1510         service_feedback.waitForVoltageOn();
1511 
1512         // If pointing had changed, do calibration
1513         if (!irq && point)
1514         {
1515             dim.log("Starting calibration.");
1516 
1517             // Calibration (2% of 20')
1518             while (!irq)
1519             {
1520                 if (irq || !takeRun("pedestal",         1000))  // 80 Hz  -> 10s
1521                     continue;
1522 //                if (irq || !takeRun("light-pulser-ext", 1000))  // 80 Hz  -> 10s
1523 //                    continue;
1524                 break;
1525             }
1526         }
1527 
1528         //console.out("  Taking data: start [5min]");
1529 
1530         // FIXME: What do we do if during calibration something has happened
1531         // e.g. drive went to ERROR? Maybe we have to check all states again?
1532 
1533         var twilight = Sun.horizon(-16).isUp;
1534 
1535         if (twilight)
1536         {
1537             for (var i=0; i<5 && !irq; i++)
1538                 takeRun("data", -1, 60); // Take data (1min)
1539         }
1540         else
1541         {
1542             var len = 300;
1543             while (!irq && len>15)
1544             {
1545                 var time = new Date();
1546                 if (takeRun("data", -1, len)) // Take data (5min)
1547                     break;
1548 
1549                 len -= parseInt((new Date()-time)/1000);
1550             }
1551         }
1552 
1553         //console.out("  Taking data: done");
1554         run++;
1555 
1556         continue; // case "DATA"
1557     }
1558 
1559     if (nextObs!=undefined && sub==obs.length-1)
1560         dim.log("Next observation will start at "+nextObs.start.toUTCString()+" [id="+nextObs.id+"]");
1561 
1562     sub++;
1563 }
1564 
1565 sub_drsruns.close();
1566 
1567 dim.log("Left main loop [irq="+irq+"]");
1568 
1569 // ================================================================
1570 // Comments and ToDo goes here
1571 // ================================================================
1572 
1573 // error handline : http://www.sitepoint.com/exceptional-exception-handling-in-javascript/
1574 // classes: http://www.phpied.com/3-ways-to-define-a-javascript-class/
1575