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

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