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

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