source: trunk/FACT++/scripts/Main.js@ 15289

Last change on this file since 15289 was 15289, checked in by tbretz, 12 years ago
Improved the code to handle the INCOMPLETE message, added some code to suppress double shutdown and allow simple tests during day-time.
File size: 36.1 KB
Line 
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
7dim.log("Start: "+__FILE__+" ["+__DATE__+"]");
8
9//dimctrl.defineState(37, "TimeOutBeforeTakingData", "MCP took more than 5minutes to start TakingData");
10
11// ================================================================
12// Code related to the schedule
13// ================================================================
14
15//this is just the class implementation of 'Observation'
16include('scripts/Observation_class.js');
17
18// this file just contains the definition of
19// the variable observations, which builds our nightly schedule, hence the filename
20include('scripts/schedule.js');
21
22// make Observation objects from user input and check if 'date' is increasing.
23for (var i=0; i<observations.length; i++)
24{
25 observations[i] = new Observation(observations[i]);
26
27 // check if the start date given by the user is increasing.
28 if (i>0 && observations[i].start <= observations[i-1].start)
29 {
30 throw new Error("Start time '"+ observations[i].start.toUTCString()+
31 "' in row "+i+" exceeds start time in row "+(i-1));
32 }
33}
34
35// Get the observation scheduled for 'now' from the table and
36// return its index
37function getObservation(now)
38{
39 if (now==undefined)
40 now = new Date();
41
42 if (isNaN(now.valueOf()))
43 throw new Error("Date argument in getObservation invalid.");
44
45 for (var i=0; i<observations.length; i++)
46 if (now<observations[i].start)
47 return i-1;
48
49 return observations.length-1;
50}
51
52// ================================================================
53// Code to check whether observation is allowed
54// ================================================================
55/*
56function currentEst(source)
57{
58 var moon = new Moon();
59 if (!moon.isUp)
60 return 7.7;
61
62 var dist = Sky.dist(moon, source);
63
64 var alt = 90-moon.toLocal().zd;
65
66 var lc = dist*alt*pow(Moon.disk(), 6)/360/360;
67
68 var cur = 7.7+4942*lc;
69
70 return cur;
71}
72
73function thresholdEst(source) // relative threshold (ratio)
74{
75 // Assumption:
76 // atmosphere is 70km, shower taks place after 60km, earth radius 6400km
77 // just using the cosine law
78 // This fits very well with MC results: See Roger Firpo, p.45
79 // "Study of the MAGIC telescope sensitivity for Large Zenith Angle observations"
80
81 var c = Math.cos(Math.Pi-source.zd);
82 var ratio = (10*sqrt(409600*c*c+9009) + 6400*c - 60)/10;
83
84 // assumption: Energy threshold increases linearily with current
85 // assumption: Energy threshold increases linearily with distance
86
87 return ratio*currentEst(source)/7.7;
88}
89*/
90
91// ----------------------------------------------------------------
92
93// ================================================================
94// Code related to monitoring the fad system
95// ================================================================
96
97var sub_incomplete = new Subscription("FAD_CONTROL/INCOMPLETE");
98
99var incomplete = 0;
100
101sub_incomplete.onchange = function(evt)
102{
103 if (!evt.data)
104 {
105 this.last_inc = 0;
106 return;
107 }
108
109 var inc = evt.obj['incomplete'];
110 if (!inc)
111 {
112 this.last_inc = 0;
113 return;
114 }
115
116 if (this.last_inc==inc)
117 return;
118
119 incomplete = inc;
120
121 var reset = [ ];
122
123 for (var i=0; i<40; i++)
124 if (inc&(1<<i))
125 {
126 console.out(" FAD %2d".$(i)+" lost during data taking.");
127 reset.push(parseInt(i/10));
128 }
129
130 if (reset.length>10)
131 return;
132
133 dim.send("MCP/RESET");
134
135 reset = reset.filter(function(elem,pos){return reset.indexOf(elem)==pos;});
136
137 if (reset.length>0)
138 {
139 console.out("");
140 console.out(" FADs belong to crate(s): "+reset);
141 console.out("");
142 }
143
144 this.last_inc = inc;
145}
146
147var sub_connections = new Subscription("FAD_CONTROL/CONNECTIONS");
148
149/**
150 * call-back function of FAD_CONTROL/CONNECTIONS
151 * store IDs of problematic FADs
152 *
153 */
154/*
155sub_connections.onchange = function(evt)
156{
157 // This happens, but why?
158 if (!evt.obj['status'])
159 return;
160
161 this.reset = [ ];
162
163 for (var x=0; x<40; x++)
164 if (evt.obj['status'][x]!=66 && evt.obj['status'][x]!=67)
165 this.reset.push(x);
166
167 if (this.reset.length==0)
168 return;
169
170 //m.alarm("FAD board loss detected...");
171 dim.send("MCP/RESET");
172 dim.send("FAD_CONTROL/CLOSE_OPEN_FILES");
173}
174*/
175
176/**
177 * reconnect to problematic FADs
178 *
179 * Dis- and Reconnects to FADs, found to be problematic by call-back function
180 * onchange() to have a different CONNECTION value than 66 or 67.
181 *
182 * @returns
183 * a boolean is returned.
184 * reconnect returns true if:
185 * * nothing needed to be reset --> no problems found by onchange()
186 * * the reconnection went fine.
187 *
188 * reconnect *never returns false* so far.
189 *
190 * @example
191 * if (!sub_connections.reconnect())
192 * exit();
193 */
194sub_connections.reconnect = function()
195{
196 // this.reset is a list containing the IDs of FADs,
197 // which have neither CONNECTION==66 nor ==67, whatever this means :-)
198 if (this.reset.length==0)
199 return true;
200
201 console.out(" Reconnect: start ["+this.reset.length+"]");
202
203 for (var i=0; i<this.reset.length; i++)
204 dim.send("FAD_CONTROL/DISCONNECT", this.reset[i]);
205
206 v8.sleep(3000);
207
208 while (this.reset.length)
209 dim.send("FAD_CONTROL/CONNECT", this.reset.pop());
210
211 v8.sleep(1000);
212 dim.wait("FAD_CONTROL", "Connected", 3000);
213
214 console.out(" Reconnect: end");
215
216 return true;
217}
218
219// ================================================================
220// Code related to taking data
221// ================================================================
222
223var startrun = new Subscription("FAD_CONTROL/START_RUN");
224startrun.get(5000);
225
226function takeRun(type, count, time)
227{
228 if (!count)
229 count = -1;
230 if (!time)
231 time = -1;
232
233 var nextrun = startrun.get().obj['next'];
234 console.out(" Take run %3d".$(nextrun)+": N="+count+" T="+time+"s ["+type+"]");
235
236 dim.send("MCP/START", time?time:-1, count?count:-1, type);
237
238 // FIXME: Replace by callback?
239 //
240 // DN: I believe instead of waiting for 'TakingData' one could split this
241 // up into two checks with an extra condition:
242 // if type == 'data':
243 // wait until ThresholdCalibration starts:
244 // --> this time should be pretty identical for each run
245 // if this takes longer than say 3s:
246 // there might be a problem with one/more FADs
247 //
248 // wait until "TakingData":
249 // --> this seems to take even some minutes sometimes...
250 // (might be optimized rather soon, but still in the moment...)
251 // if this takes way too long:
252 // there might be something broken,
253 // so maybe a very high time limit is ok here.
254 // I think there is not much that can go wrong,
255 // when the Thr-Calib has already started. Still it might be nice
256 // If in the future RateControl is written so to find out that
257 // in case the threshold finding algorithm does
258 // *not converge as usual*
259 // it can complain, and in this way give a hint, that the weather
260 // might be a little bit too bad.
261 // else:
262 // wait until "TakingData":
263 // --> in a non-data run this time should be pretty short again
264 // if this takes longer than say 3s:
265 // there might be a problem with one/more FADs
266 //
267
268 // Use this if you use the rate control to calibrate by rates
269 //if (!dim.wait("MCP", "TakingData", -300000) )
270 //{
271 // throw new Error("MCP took longer than 5 minutes to start TakingData"+
272 // "maybe this idicates a problem with one of the FADs?");
273 //}
274
275 // Here we could check and handle fad losses
276 try
277 {
278 dim.wait("MCP", "TakingData", 15000);
279 }
280 catch (e)
281 {
282 console.out("");
283 console.out("MCP: "+dim.state("MCP").name);
284 console.out("FAD_CONTROL: "+dim.state("FAD_CONTROL").name);
285 console.out("FTM_CONTROL: "+dim.state("FTM_CONTROL").name);
286 console.out("");
287
288 if (dim.state("MCP").name!="Configuring3" ||
289 dim.state("FAD_CONTROL").name!="Configuring2")
290 throw e;
291
292 console.out("");
293 console.out("Waiting for fadctrl to get configured timed out... checking for in-run FAD loss.");
294
295 var con = sub_connections.get();
296 var stat = con.obj['status'];
297
298 console.out("Sending MCP/RESET");
299 dim.send("MCP/RESET");
300
301 dim.wait("FTM_CONTROL", "Idle", 3000);
302 dim.wait("FAD_CONTROL", "Connected", 3000);
303 dim.wait("MCP", "Idle", 3000);
304
305 var reset = [ ];
306
307 for (var i=0; i<40; i++)
308 if (stat[i]!=0x43)
309 {
310 console.out(" FAD %2d".$(i)+" not in Configured state.");
311 reset.push(parseInt(i/10));
312 }
313
314 reset = reset.filter(function(elem,pos){return reset.indexOf(elem)==pos;});
315
316 if (reset.length>0)
317 {
318 console.out("");
319 console.out(" FADs belong to crate(s): "+reset);
320 console.out("");
321 }
322
323 console.out("");
324 console.out("Trying automatic reconnect...");
325
326 for (var i=0; i<40; i++)
327 if (stat[i]!=0x43)
328 {
329 console.out(" ...disconnect "+i);
330 dim.send("FAD_CONTROL/DISCONNECT", i);
331 }
332
333 console.out(" ...waiting for 5s");
334 v8.sleep(5000);
335
336 for (var i=0; i<40; i++)
337 if (stat[i]!=0x43)
338 {
339 console.out(" ...reconnect "+i);
340 dim.send("FAD_CONTROL/CONNECT", i);
341 }
342
343 console.out(" ...waiting for 1s");
344 v8.sleep(1000);
345 console.out("");
346
347 throw e;
348 }
349
350 dim.wait("MCP", "Idle", time>0 ? time*1250 : undefined); // run time plus 25%
351
352 if (incomplete)
353 {
354 console.out("");
355 console.out("Trying automatic reconnect...");
356
357 for (var i=0; i<40; i++)
358 if (incomplete&(1<<i))
359 {
360 console.out(" ...disconnect "+i);
361 dim.send("FAD_CONTROL/DISCONNECT", i);
362 }
363
364 console.out(" ...waiting for 5s");
365 v8.sleep(5000);
366
367 for (var i=0; i<40; i++)
368 if (incomplete&(1<<i))
369 {
370 console.out(" ...reconnect "+i);
371 dim.send("FAD_CONTROL/CONNECT", i);
372 }
373
374 console.out(" ...waiting for 1s");
375 v8.sleep(1000);
376 console.out("");
377
378 incomplete = 0;
379
380 throw new Error("In-run FAD loss detected.");
381 }
382
383 //console.out(" Take run: end");
384
385 // DN: currently reconnect() never returns false
386 // .. but it can fail of course.
387 //if (!sub_connections.reconnect())
388 // exit();
389
390 return true;//sub_connections.reconnect();
391}
392
393// ----------------------------------------------------------------
394
395function doDrsCalibration(where)
396{
397 console.out(" Take DRS calibration ["+where+"]");
398
399 service_feedback.voltageOff();
400
401 var tm = new Date();
402
403 while (1)
404 {
405 dim.send("FAD_CONTROL/START_DRS_CALIBRATION");
406 if (!takeRun("drs-pedestal", 1000)) // 40 / 20s (50Hz)
407 continue;
408 if (!takeRun("drs-gain", 1000)) // 40 / 20s (50Hz)
409 continue;
410 if (!takeRun("drs-pedestal", 1000)) // 40 / 20s (50Hz)
411 continue;
412
413 dim.send("FAD_CONTROL/SET_FILE_FORMAT", 2);
414 if (!takeRun("drs-pedestal", 1000)) // 40 / 20s (50Hz)
415 continue;
416 if (!takeRun("drs-time", 1000)) // 40 / 20s (50Hz)
417 continue;
418
419 dim.send("FAD_CONTROL/RESET_SECONDARY_DRS_BASELINE");
420 if (!takeRun("pedestal", 1000)) // 40 / 10s (80Hz)
421 continue;
422
423 dim.send("FAD_CONTROL/SET_FILE_FORMAT", 2);
424 if (!takeRun("pedestal", 1000)) // 40 / 10s (80Hz)
425 continue;
426 // -----------
427 // 4'40 / 2'00
428
429 break;
430 }
431
432 console.out(" DRS calibration done [%.1f]".$((new Date()-tm)/1000));
433}
434
435// ================================================================
436// Code related to the lid
437// ================================================================
438
439function OpenLid()
440{
441 /*
442 while (Sun.horizon(-13).isUp)
443 {
444 var now = new Date();
445 var minutes_until_sunset = (Sun.horizon(-13).set - now)/60000;
446 console.out(now.toUTCString()+": Sun above FACT-horizon, lid cannot be opened: sleeping 1min, remaining %.1fmin".$(minutes_until_sunset));
447 v8.sleep(60000);
448 }*/
449
450 var isClosed = dim.state("LID_CONTROL").name=="Closed";
451
452 var tm = new Date();
453
454 // Wait for lid to be open
455 if (isClosed)
456 {
457 console.out(" Open lid: start");
458 dim.send("LID_CONTROL/OPEN");
459 }
460 dim.wait("LID_CONTROL", "Open", 30000);
461
462 if (isClosed)
463 console.out(" Open lid: done [%.1fs]".$((new Date()-tm)/1000));
464}
465
466function CloseLid()
467{
468 var isOpen = dim.state("LID_CONTROL").name=="Open";
469
470 var tm = new Date();
471
472 // Wait for lid to be open
473 if (isOpen)
474 {
475 console.out(" Close lid: start");
476 dim.send("LID_CONTROL/CLOSE");
477 }
478 dim.wait("LID_CONTROL", "Closed", 30000);
479
480 if (isOpen)
481 console.out(" Close lid: end [%.1fs]".$((new Date()-tm)/1000));
482}
483
484// ================================================================
485// Code related to switching bias voltage on and off
486// ================================================================
487
488var service_feedback = new Subscription("FEEDBACK/DEVIATION");
489
490service_feedback.onchange = function(evt)
491{
492 if (this.cnt && evt.counter>this.cnt+12)
493 return;
494
495 this.voltageStep = null;
496 if (!evt.obj)
497 return;
498
499 var delta = evt.obj['DeltaBias'];
500
501 var avg = 0;
502 for (var i=0; i<320; i++)
503 avg += delta[i];
504 avg /= 320;
505
506 if (this.previous)
507 this.voltageStep = Math.abs(avg-this.previous);
508
509 this.previous = avg;
510
511 console.out(" DeltaV="+this.voltageStep);
512}
513
514// DN: Why is voltageOff() implemented as
515// a method of a Subscription to a specific Service
516// I naively would think of voltageOff() as an unbound function.
517// I seems to me it has to be a method of a Subscription object, in order
518// to use the update counting method. But does it have to be
519// a Subscription to FEEDBACK/DEVIATION, or could it work with other services as well?
520service_feedback.voltageOff = function()
521{
522 var state = dim.state("BIAS_CONTROL").name;
523
524 // check of feedback has to be switched on
525 var isOn = state=="VoltageOn" || state=="Ramping";
526 if (isOn)
527 {
528 console.out(" Voltage off: start");
529
530 // Supress the possibility that the bias control is
531 // ramping and will reject the command to switch the
532 // voltage off
533 var isControl = dim.state("FEEDBACK").name=="CurrentControl";
534 if (isControl)
535 {
536 console.out(" Suspending feedback.");
537 dim.send("FEEDBACK/ENABLE_OUTPUT", false);
538 dim.wait("FEEDBACK", "CurrentCtrlIdle", 3000);
539 }
540
541 // Switch voltage off
542 console.out(" Voltage on: switch off");
543 dim.send("BIAS_CONTROL/SET_ZERO_VOLTAGE");
544
545 // If the feedback was enabled, re-enable it
546 if (isControl)
547 {
548 console.out(" Resuming feedback.");
549 dim.send("FEEDBACK/ENABLE_OUTPUT", true);
550 dim.wait("FEEDBACK", "CurrentControl", 3000);
551 }
552 }
553
554 dim.wait("BIAS_CONTROL", "VoltageOff", 5000);
555
556 // FEEDBACK stays in CurrentCtrl when Voltage is off but output enabled
557 // dim.wait("FEEDBACK", "CurrentCtrlIdle", 1000);
558
559 if (isOn)
560 console.out(" Voltage off: end");
561}
562
563// DN: The name of the method voltageOn() in the context of the method
564// voltageOff() is a little bit misleading, since when voltageOff() returns
565// the caller can be sure the voltage is off, but when voltageOn() return
566// this is not the case, in the sense, that the caller can now take data.
567// instead the caller of voltageOn() *must* call waitForVoltageOn() afterwards
568// in order to safely take good-quality data.
569// This could lead to nasty bugs in the sense, that the second call might
570// be forgotten by somebody
571//
572// so I suggest to rename voltageOn() --> prepareVoltageOn()
573// waitForVoltageOn() stays as it is
574// and one creates a third method called:voltageOn() like this
575/* service_feedback.voltageOn = function()
576 * {
577 * this.prepareVoltageOn();
578 * this.waitForVoltageOn();
579 * }
580 *
581 * */
582// For convenience.
583
584service_feedback.voltageOn = function()
585{
586 //if (Sun.horizon("FACT").isUp)
587 // throw new Error("Sun is above FACT-horizon, voltage cannot be switched on.");
588
589 var isOff = dim.state("BIAS_CONTROL").name=="VoltageOff";
590 if (isOff)
591 {
592 console.out(" Voltage on: switch on");
593 //console.out(JSON.stringify(dim.state("BIAS_CONTROL")));
594
595 dim.send("BIAS_CONTROL/SET_GLOBAL_DAC", 1);
596 }
597
598 // Wait until voltage on
599 dim.wait("BIAS_CONTROL", "VoltageOn", 5000);
600
601 // From now on the feedback waits for a valid report from the FSC
602 // and than switchs to CurrentControl
603 dim.wait("FEEDBACK", "CurrentControl", 60000);
604
605 if (isOff)
606 {
607 console.out(" Voltage on: cnt="+this.cnt);
608
609 this.previous = undefined;
610 this.cnt = this.get().counter;
611 this.voltageStep = undefined;
612 }
613}
614
615service_feedback.waitForVoltageOn = function()
616{
617 // waiting 45sec for the current control to stabilize...
618 // v8.sleep(45000);
619
620 // ----- Wait for at least three updates -----
621 // The feedback is started as if the camera where at 0deg
622 // Then after the first temp update, the temperature will be set to the
623 // correct value (this has already happened)
624 // So we only have to wait for the current to get stable.
625 // This should happen after three to five current updates.
626 // So we want one recent temperature update
627 // and three recent current updates
628
629 // Avoid output if condition is already fulfilled
630 if (this.cnt && this.get().counter>this.cnt+10)
631 return;
632
633 // FIXME: timeout missing
634 console.out(" Feedback wait: start");
635
636 function func(service)
637 {
638 if ((service.cnt!=undefined && service.get().counter>service.cnt+10) ||
639 (service.voltageStep && service.voltageStep<0.02))
640 return true;
641 }
642
643 var now = new Date();
644 //v8.timeout(5*60000, func, this);
645 while ((this.cnt==undefined || this.get().counter<=this.cnt+10) && (!this.voltageStep || this.voltageStep>0.02))
646 v8.sleep();
647
648 console.out(" Feedback wait: end [dV=%.3f, cnt=%d, %.2fs]".$(this.voltageStep, this.get().counter, (new Date()-now)/1000));
649}
650
651// ================================================================
652// Function to shutdown the system
653// ================================================================
654
655function Shutdown()
656{
657 console.out("Shutdown: start");
658
659 service_feedback.voltageOff();
660 CloseLid();
661 dim.send("DRIVE_CONTROL/PARK");
662
663 console.out("Waiting for telescope to park. This may take a while.");
664
665 // FIXME: This might not work is the drive is already close to park position
666 dim.wait("DRIVE_CONTROL", "Locked", 3000);
667
668 var sub = new Subscription("DRIVE_CONTROL/POINTING_POSITION");
669 sub.get(5000); // FIXME: Proper error message in case of failure
670
671 function func()
672 {
673 var report = sub.get();
674
675 var zd = report.obj['Zd'];
676 var az = report.obj['Az'];
677
678 if (zd>100 && Math.abs(az)<1)
679 return true;
680
681 return undefined;
682 }
683
684 var now = new Date();
685 v8.timeout(150000, func);
686
687 //dim.send("FEEDBACK/STOP");
688 dim.send("FEEDBACK/ENABLE_OUTPUT", false);
689 dim.send("FTM_CONTROL/STOP_TRIGGER");
690
691 dim.wait("FEEDBACK", "CurrentCtrlIdle", 3000);
692 dim.wait("FTM_CONTROL", "Idle", 3000);
693
694 var report = sub.get();
695
696 console.out("");
697 console.out("Shutdown procedure seems to be finished...");
698 console.out(" Telescope at Zd=%.1fdeg Az=%.1fdeg".$(report.obj['Zd'], report.obj['Az']));
699 console.out(" Please make sure the park position was reached");
700 console.out(" and the telescope is not moving anymore.");
701 console.out(" Please check that the lid is closed and the voltage switched off.");
702 console.out("");
703 console.out("Shutdown: end ["+(new Date()-now)/1000+"s]");
704
705 sub.close();
706}
707
708// ================================================================
709// Crosscheck all states
710// ================================================================
711
712// ----------------------------------------------------------------
713// Do a standard startup to bring the system in into a well
714// defined state
715// ----------------------------------------------------------------
716include('scripts/Startup.js');
717
718// ----------------------------------------------------------------
719// Check that everything we need is availabel to receive commands
720// (FIXME: Should that go to the general CheckState?)
721// ----------------------------------------------------------------
722console.out("Checking send.");
723checkSend(["MCP", "DRIVE_CONTROL", "LID_CONTROL", "FAD_CONTROL", "FEEDBACK"]);
724console.out("Checking send: done");
725
726// ----------------------------------------------------------------
727// Bring feedback into the correct operational state
728// ----------------------------------------------------------------
729console.out("Feedback init: start.");
730service_feedback.get(5000);
731
732dim.send("FEEDBACK/ENABLE_OUTPUT", true);
733dim.send("FEEDBACK/START_CURRENT_CONTROL", 0.);
734
735v8.timeout(3000, function() { var n = dim.state("FEEDBACK").name; if (n=="CurrentCtrlIdle" || n=="CurrentControl") return true; });
736
737// ----------------------------------------------------------------
738// Connect to the DRS_RUNS service
739// ----------------------------------------------------------------
740console.out("Drs runs init: start.");
741
742var sub_drsruns = new Subscription("FAD_CONTROL/DRS_RUNS");
743sub_drsruns.get(5000);
744// FIXME: Check if the last DRS calibration was complete?
745
746function getTimeSinceLastDrsCalib()
747{
748 // ----- Time since last DRS Calibration [min] ------
749 var runs = sub_drsruns.get(0);
750 var diff = (new Date()-runs.time)/60000;
751
752 // Warning: 'roi=300' is a number which is not intrisically fixed
753 // but can change depending on the taste of the observers
754 var valid = runs.obj['run'][2]>0 && runs.obj['roi']==300;
755
756 if (valid)
757 console.out(" Last DRS calib: %.1fmin ago".$(diff));
758 else
759 console.out(" No valid drs calibration available");
760
761 return valid ? diff : null;
762}
763
764// ----------------------------------------------------------------
765// Make sure we will write files
766// ----------------------------------------------------------------
767dim.send("FAD_CONTROL/SET_FILE_FORMAT", 2);
768
769// ----------------------------------------------------------------
770// Print some information for the user about the
771// expected first oberservation
772// ----------------------------------------------------------------
773var test = getObservation();
774if (test!=undefined)
775{
776 var n = new Date();
777 if (test==-1)
778 console.out(n.toUTCString()+": First observation scheduled for "+observations[0].start.toUTCString());
779 if (test>=0 && test<observations.length)
780 console.out(n.toUTCString()+": First observation should start immediately.");
781 if (observations[0].start>n+12*3600*1000)
782 console.out(n.toUTCString()+": No observations scheduled for the next 12 hours!");
783}
784
785// ----------------------------------------------------------------
786// Start main loop
787// ----------------------------------------------------------------
788console.out("Start main loop.");
789
790var run = -2; // getObservation never called
791var sub;
792var lastObs;
793var sun = Sun.horizon(-13);
794var system_on; // undefined
795
796while (1)
797{
798 // Check if observation position is still valid
799 // If source position has changed, set run=0
800 var idxObs = getObservation();
801 if (idxObs===undefined)
802 break;
803
804 // we are still waiting for the first observation in the schedule
805 if (idxObs==-1)
806 {
807 // flag that the first observation will be in the future
808 run = -1;
809 v8.sleep(1000);
810 continue;
811 }
812
813 // Check if we have to take action do to sun-rise
814 var was_up = sun.isUp;
815 sun = Sun.horizon(-13);
816 if (!was_up && sun.isUp)
817 {
818 console.out("Sun rise detected.... automatic shutdown initiated!");
819 // FIXME: State check?
820 Shutdown();
821 system_on = false;
822 continue;
823 }
824
825 // Current and next observation target
826 var obs = observations[idxObs];
827 var nextObs = observations[idxObs+1];
828
829 // Check if observation target has changed
830 if (lastObs!=idxObs) // !Object.isEqual(obs, nextObs)
831 {
832 console.out("--- "+idxObs+" ---");
833 console.out("Current time: "+new Date().toUTCString());
834 console.out("Current observation: "+obs.start.toUTCString());
835 if (nextObs!=undefined)
836 console.out("Next observation: "+nextObs.start.toUTCString());
837 console.out("");
838
839 // This is the first source, but we do not come from
840 // a scheduled 'START', so we have to check if the
841 // telescop is operational already
842 sub = 0;
843 if (run<0)
844 {
845 //Startup(); // -> Bias On/Off?, Lid open/closed?
846 //CloseLid();
847 }
848
849 // The first observation had a start-time in the past...
850 // In this particular case start with the last entry
851 // in the list of measurements
852 if (run==-2)
853 sub = obs.length-1;
854
855 run = 0;
856 }
857 lastObs = idxObs;
858
859 if (nextObs==undefined && obs[obs.length-1].task!="SHUTDOWN")
860 throw Error("Last scheduled measurement must be a shutdown.");
861
862 // We are done with all measurement slots for this
863 // observation... wait for next observation
864 if (sub>=obs.length)
865 {
866 v8.sleep(1000);
867 continue;
868 }
869
870 var task = obs[sub].task;
871
872 // Check if sun is still up... only DATA and RATESCAN must be suppressed
873 if ((task=="DATA" || task=="RATESCAN") && sun.isUp)
874 {
875 var now = new Date();
876 var remaining = (sun.set - now)/60000;
877 console.out(now.toUTCString()+" - "+obs[sub].task+": Sun above FACT-horizon: sleeping 1min, remaining %.1fmin".$(remaining));
878 v8.sleep(60000);
879 continue;
880 }
881
882 if (system_on===false && task!="STARTUP")
883 {
884 v8.sleep(1000);
885 continue;
886 }
887
888 var power_states = sun.isUp || system_on===false ? [ "DriveOff" ] : [ "SystemOn" ];
889
890 var drive_states = sun.isUp || system_on===false ? [ ] : [ "Armed", "Tracking", "OnTrack" ];
891
892 // A scheduled task was found, lets check if all servers are
893 // still only and in reasonable states. If this is not the case,
894 // something unexpected must have happend and the script is aborted.
895 //console.out(" Checking states [general]");
896 var table =
897 [
898 [ "TNG_WEATHER" ],
899 [ "MAGIC_WEATHER" ],
900 [ "CHAT" ],
901 [ "SMART_FACT" ],
902 [ "DATA_LOGGER", [ "NightlyFileOpen", "WaitForRun", "Logging" ] ],
903 [ "FSC_CONTROL", [ "Connected" ] ],
904 [ "MCP", [ "Idle" ] ],
905 [ "TIME_CHECK", [ "Valid" ] ],
906 [ "PWR_CONTROL", power_states/*[ "SystemOn" ]*/ ],
907 [ "AGILENT_CONTROL", [ "VoltageOn" ] ],
908 [ "BIAS_CONTROL", [ "VoltageOff", "VoltageOn", "Ramping" ] ],
909 [ "FEEDBACK", [ "CurrentControl", "CurrentCtrlIdle" ] ],
910 [ "LID_CONTROL", [ "Open", "Closed" ] ],
911 [ "DRIVE_CONTROL", drive_states/*[ "Armed", "Tracking", "OnTrack" ]*/ ],
912 [ "FTM_CONTROL", [ "Idle", "TriggerOn" ] ],
913 [ "FAD_CONTROL", [ "Connected", "WritingData" ] ],
914 [ "RATE_SCAN", [ "Connected" ] ],
915 [ "RATE_CONTROL", [ "Connected", "GlobalThresholdSet", "InProgress" ] ],
916 ];
917
918 if (!checkStates(table))
919 {
920 throw new Error("Something unexpected has happened. One of the servers"+
921 "is in a state in which it should not be. Please,"+
922 "try to find out what happened...");
923 }
924
925 console.out("\n"+(new Date()).toUTCString()+": Current measurement: "+obs[sub]);
926
927 // Check if obs.task is one of the one-time-tasks
928 switch (obs[sub].task)
929 {
930 case "STARTUP":
931 console.out(" STARTUP", "");
932 CloseLid();
933
934 doDrsCalibration("startup"); // will switch the voltage off
935
936 service_feedback.voltageOn();
937 service_feedback.waitForVoltageOn();
938
939 // Before we can switch to 3000 we have to make the right DRS calibration
940 console.out(" Take single p.e. run.");
941 while (!takeRun("pedestal", 5000));
942
943 // It is unclear what comes next, so we better switch off the voltage
944 service_feedback.voltageOff();
945 system_on = true;
946 break;
947
948 case "SHUTDOWN":
949 console.out(" SHUTDOWN", "");
950 Shutdown();
951 system_on = false;
952
953 // FIXME: Avoid new observations after a shutdown until
954 // the next startup (set run back to -2?)
955 console.out(" Waiting for next startup.", "");
956 sub++;
957 continue;
958
959 case "IDLE":
960 v8.sleep(1000);
961 continue;
962
963 case "DRSCALIB":
964 console.out(" DRSCALIB", "");
965 doDrsCalibration("drscalib"); // will switch the voltage off
966 break;
967
968 case "SINGLEPE":
969 console.out(" SINGLE-PE", "");
970
971 // The lid must be closes
972 CloseLid();
973
974 // Check if DRS calibration is necessary
975 var diff = getTimeSinceLastDrsCalib();
976 if (diff>30 || diff==null)
977 doDrsCalibration("singlepe"); // will turn voltage off
978
979 // The voltage must be on
980 service_feedback.voltageOn();
981 service_feedback.waitForVoltageOn();
982
983 // Before we can switch to 3000 we have to make the right DRS calibration
984 console.out(" Take single p.e. run.");
985 while (!takeRun("pedestal", 5000));
986
987 // It is unclear what comes next, so we better switch off the voltage
988 service_feedback.voltageOff();
989 break;
990
991 case "RATESCAN":
992 console.out(" RATESCAN", "");
993
994 var tm1 = new Date();
995
996 // This is a workaround to make sure that we really catch
997 // the new state and not the old one
998 dim.send("DRIVE_CONTROL/STOP");
999 dim.wait("DRIVE_CONTROL", "Armed", 5000);
1000
1001 // The lid must be open
1002 OpenLid();
1003
1004 // The voltage must be switched on
1005 service_feedback.voltageOn();
1006
1007 if (obs.source != undefined)
1008 dim.send("DRIVE_CONTROL/TRACK_ON", obs[sub].source);
1009 else
1010 dim.send("DRIVE_CONTROL/TRACK", obs[sub].ra, obs[sub].dec);
1011
1012 dim.wait("DRIVE_CONTROL", "OnTrack", 150000); // 110s for turning and 30s for stabilizing
1013
1014 service_feedback.waitForVoltageOn();
1015
1016 var tm2 = new Date();
1017
1018 // Start rate scan
1019 dim.send("RATE_SCAN/START_THRESHOLD_SCAN", 50, 1000, -10);
1020
1021 // Lets wait if the ratescan really starts... this might take a few
1022 // seconds because RATE_SCAN configures the ftm and is waiting for
1023 // it to be configured.
1024 dim.wait("RATE_SCAN", "InProgress", 10000);
1025 dim.wait("RATE_SCAN", "Connected", 2700000);
1026
1027 // this line is actually some kind of hack.
1028 // after the Ratescan, no data is written to disk. I don't know why, but it happens all the time
1029 // So I decided to put this line here as a kind of patchwork....
1030 //dim.send("FAD_CONTROL/SET_FILE_FORMAT", 2);
1031
1032 console.out(" Ratescan done [%.1fs, %.1fs]".$((tm2-tm1)/1000, (new Date()-tm2)/1000));
1033 break; // case "RATESCAN"
1034
1035 case "DATA":
1036
1037 // ========================== case "DATA" ============================
1038 /*
1039 if (Sun.horizon("FACT").isUp)
1040 {
1041 console.out(" SHUTDOWN","");
1042 Shutdown();
1043 console.out(" Exit forced due to broken schedule", "");
1044 exit();
1045 }
1046 */
1047 // Calculate remaining time for this observation in minutes
1048 var remaining = nextObs==undefined ? 0 : (nextObs.start-new Date())/60000;
1049
1050 // ------------------------------------------------------------
1051
1052 console.out(" Run #"+run+" (remaining "+parseInt(remaining)+"min)");
1053
1054 // ----- Time since last DRS Calibration [min] ------
1055 var diff = getTimeSinceLastDrsCalib();
1056
1057 // Changine pointing position and take calibration...
1058 // ...every four runs (every ~20min)
1059 // ...if at least ten minutes of observation time are left
1060 // ...if this is the first run on the source
1061 var point = (run%4==0 && remaining>10) || run==0;
1062
1063 // Take DRS Calib...
1064 // ...every four runs (every ~20min)
1065 // ...at last every two hours
1066 // ...when DRS temperature has changed by more than 2deg (?)
1067 // ...when more than 15min of observation are left
1068 // ...no drs calibration was done yet
1069 var drscal = (run%4==0 && (remaining>15 && diff>70)) || diff==null;
1070
1071 if (point)
1072 {
1073 // Change wobble position every four runs,
1074 // start with alternating wobble positions each day
1075 var wobble = (parseInt(run/4) + parseInt(new Date()/1000/3600/24-0.5))%2+1;
1076
1077 //console.out(" Move telescope to '"+source+"' "+offset+" "+wobble);
1078 console.out(" Move telescope to '"+obs[sub].source+"' ["+wobble+"]");
1079
1080 //var offset = observations[obs][2];
1081 //var wobble = observations[obs][3 + parseInt(run/4)%2];
1082
1083 //dim.send("DRIVE_CONTROL/TRACK_SOURCE", offset, wobble, source);
1084
1085 dim.send("DRIVE_CONTROL/TRACK_WOBBLE", wobble, obs[sub].source);
1086
1087 // Do we have to check if the telescope is really moving?
1088 // We can cross-check the SOURCE service later
1089 }
1090
1091 if (drscal)
1092 doDrsCalibration("data"); // will turn voltage off
1093
1094 OpenLid();
1095
1096 // voltage must be switched on after the lid is open for the
1097 // feedback to adapt the voltage properly to the night-sky
1098 // background light level.
1099 service_feedback.voltageOn();
1100
1101 // This is now th right time to wait for th drive to be stable
1102 dim.wait("DRIVE_CONTROL", "OnTrack", 150000); // 110s for turning and 30s for stabilizing
1103
1104 // Now we have to be prepared for data-taking:
1105 // make sure voltage is on
1106 service_feedback.waitForVoltageOn();
1107
1108 // If pointing had changed, do calibration
1109 if (point)
1110 {
1111 console.out(" Calibration.");
1112
1113 // Calibration (2% of 20')
1114 while (1)
1115 {
1116 if (!takeRun("pedestal", 1000)) // 80 Hz -> 10s
1117 continue;
1118 if (!takeRun("light-pulser-ext", 1000)) // 80 Hz -> 10s
1119 continue;
1120 break;
1121 }
1122 }
1123
1124 console.out(" Taking data: start [5min]");
1125
1126 var len = 300;
1127 while (len>0)
1128 {
1129 var time = new Date();
1130 if (takeRun("data", -1, len)) // Take data (5min)
1131 break;
1132
1133 len -= parseInt((new Date()-time)/1000);
1134 }
1135
1136 console.out(" Taking data: done");
1137 run++;
1138
1139 continue; // case "DATA"
1140 }
1141
1142 if (nextObs!=undefined && sub==obs.length-1)
1143 console.out(" Waiting for next observation scheduled for "+nextObs.start.toUTCString(),"");
1144
1145 sub++;
1146}
1147
1148sub_drsruns.close();
1149
1150// ================================================================
1151// Comments and ToDo goes here
1152// ================================================================
1153
1154// error handline : http://www.sitepoint.com/exceptional-exception-handling-in-javascript/
1155// classes: http://www.phpied.com/3-ways-to-define-a-javascript-class/
1156//
1157// Arguments: TakeFirstDrsCalib
1158// To be determined: How to stop the script without foreceful interruption?
Note: See TracBrowser for help on using the repository browser.