source: trunk/FACT++/scripts/MainClassic.js@ 18514

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