1 /** 2 * @fileOverview This file has functions related to documenting JavaScript. 3 * @author <a href="mailto:thomas.bretz@epfl.ch">Thomas Bretz</a> 4 */ 5 'use strict'; 6 7 dim.log("Start: "+__FILE__+" ["+__DATE__+"]"); 8 9 //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' 16 include('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 20 include('scripts/schedule.js'); 21 22 // make Observation objects from user input and check if 'date' is increasing. 23 for (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 37 function 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 /* 56 function 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 73 function 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 97 var sub_incomplete = new Subscription("FAD_CONTROL/INCOMPLETE"); 98 99 var incomplete = 0; 100 101 sub_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 122 var 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 /* 130 sub_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 */ 169 sub_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 198 var startrun = new Subscription("FAD_CONTROL/START_RUN"); 199 startrun.get(5000); 200 201 function 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 240 function 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 392 function 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 446 function 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 473 function 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 495 var service_feedback = new Subscription("FEEDBACK/DEVIATION"); 496 497 service_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? 527 service_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 591 service_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 622 service_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 662 function 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 719 var datalogger_subscriptions = new Subscription("DATA_LOGGER/SUBSCRIPTIONS"); 720 datalogger_subscriptions.get(3000, false); 721 722 datalogger_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 // ---------------------------------------------------------------- 819 include('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 // ---------------------------------------------------------------- 825 console.out("Checking send."); 826 checkSend(["MCP", "DRIVE_CONTROL", "LID_CONTROL", "FAD_CONTROL", "FEEDBACK"]); 827 console.out("Checking send: done"); 828 829 // ---------------------------------------------------------------- 830 // Bring feedback into the correct operational state 831 // ---------------------------------------------------------------- 832 console.out("Feedback init: start."); 833 service_feedback.get(5000); 834 835 dim.send("FEEDBACK/ENABLE_OUTPUT", true); 836 dim.send("FEEDBACK/START_CURRENT_CONTROL", 0.); 837 838 v8.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 // ---------------------------------------------------------------- 843 console.out("Drs runs init: start."); 844 845 var sub_drsruns = new Subscription("FAD_CONTROL/DRS_RUNS"); 846 sub_drsruns.get(5000); 847 // FIXME: Check if the last DRS calibration was complete? 848 849 function 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 // ---------------------------------------------------------------- 870 dim.send("FAD_CONTROL/SET_FILE_FORMAT", 2); 871 872 // ---------------------------------------------------------------- 873 // Print some information for the user about the 874 // expected first oberservation 875 // ---------------------------------------------------------------- 876 var test = getObservation(); 877 if (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 // ---------------------------------------------------------------- 891 console.out("Start main loop."); 892 893 var run = -2; // getObservation never called 894 var sub; 895 var lastObs; 896 var sun = Sun.horizon(-13); 897 var system_on; // undefined 898 899 while (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 1253 sub_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? 1264