/** * @fileOverview This file has functions related to documenting JavaScript. * @author Thomas Bretz */ 'use strict'; //this is just the class implementation of 'Observation' include('scripts/Observation_class.js'); dimctrl.defineState(37, "TimeOutBeforeTakingData", "MCP took more than 5minutes to start TakingData"); // error handline : http://www.sitepoint.com/exceptional-exception-handling-in-javascript/ // clases: http://www.phpied.com/3-ways-to-define-a-javascript-class/ // // Arguments: TakeFirstDrsCalib // To be determined: How to stop the script without foreceful interruption? // adapt states of drivectrl // // SCRIPTS: // // - Startup, e.g. do not open LID // // - Bring telescop to state 'operational', e.g. open lid, // when first run != START is detected in the loop // // - Take data // // - Shutdown // // ----> Does smartfact display alarm messages already? // ----> How to display errors in smartfact? Is that necessary at all? // (red output? maybe just ...? // // ---------------------------------------------------------------- function currentEst(source) { var moon = new Moon(); if (!moon.isUp) return 7.7; var dist = Sky.dist(moon, source); var alt = 90-moon.toLocal().zd; var lc = dist*alt*pow(Moon.disk(), 6)/360/360; var cur = 7.7+4942*lc; return cur; } function thresholdEst(source) // relative threshold (ratio) { // Assumption: // atmosphere is 70km, shower taks place after 60km, earth radius 6400km // just using the cosine law // This fits very well with MC results: See Roger Firpo, p.45 // "Study of the MAGIC telescope sensitivity for Large Zenith Angle observations" var c = Math.cos(Math.Pi-source.zd); var ratio = (10*sqrt(409600*c*c+9009) + 6400*c - 60)/10; // assumption: Energy threshold increases linearily with current // assumption: Energy threshold increases linearily with distance return ratio*currentEst(source)/7.7; } // Ratio in rate would be (estimate, not precise calculation) // pow(ratio, -0.7) // ---------------------------------------------------------------- //DN: the name of the Subscription object 'service_con' is not really // telling that its a subscription to FAD_CONTROL/CONNECTIONS // fad_connections sound ok for be, since // fad_connections.onchange() is pretty clear // fad_connections.reconnect() is not really good, but at least it has FAD in it. var service_con = new Subscription("FAD_CONTROL/CONNECTIONS"); /** * call-back function of FAD_CONTROL/CONNECTIONS * store IDs of problematic FADs * */ service_con.onchange = function(evt) { this.reset = [ ]; for (var x=0; x<40; x++) if (evt.obj['status'][x]!=66 && evt.obj['status'][x]!=67) this.reset.push(x); if (this.reset.length==0) return; dim.alarm("FAD board loss detected..."); dim.send("MCP/RESET"); dim.send("FAD_CONTROL/CLOSE_OPEN_FILES"); } /** * reconnect to problematic FADs * * Dis- and Reconnects to FADs, found to be problematic by call-back function * onchange() to have a different CONNECTION value than 66 or 67. * * @returns * a boolean is returned. * reconnect returns true if: * * nothing needed to be reset --> no problems found by onchange() * * the reconnection went fine. * * reconnect *never returns false* so far. * * @example * if (!service_con.reconnect()) * exit(); */ service_con.reconnect = function() { // this.reset is a list containing the IDs of FADs, // which have neither CONNECTION==66 nor ==67, whatever this means :-) if (this.reset.length==0) return true; console.out(" Reconnect: start ["+this.reset.length+"]"); for (var i=0; i this time should be pretty identical for each run // if this takes longer than say 3s: // there might be a problem with one/more FADs // // wait until "TakingData": // --> this seems to take even some minutes sometimes... // (might be optimized rather soon, but still in the moment...) // if this takes way too long: // there might be something broken, // so maybe a very high time limit is ok here. // I think there is not much that can go wrong, // when the Thr-Calib has already started. Still it might be nice // If in the future RateControl is written so to find out that // in case the threshold finding algorithm does // *not converge as usual* // it can complain, and in this way give a hint, that the weather // might be a little bit too bad. // else: // wait until "TakingData": // --> in a non-data run this time should be pretty short again // if this takes longer than say 3s: // there might be a problem with one/more FADs // if (!dim.wait("MCP", "TakingData", -300000) ) { console.out("MCP took longer than 5 minutes to start TakingData"); console.out("maybe this idicates a problem with one of the FADs?"); dimctrl.setState(37); dim.wait("MCP", "TakingData", 500); } dim.wait("MCP", "Idle"); console.out(" Take run: end"); // DN: currently reconnect() never returns false // .. but it can fail of course. if (!service_con.reconnect()) exit(); return true;//service_con.reconnect(); } // ---------------------------------------------------------------- function doDrsCalibration() { console.out(" DRS cal: start"); service_feedback.voltageOff(); while (1) { dim.send("FAD_CONTROL/START_DRS_CALIBRATION"); if (!takeRun("drs-pedestal", 1000)) // 40 / 20s (50Hz) continue; if (!takeRun("drs-gain", 1000)) // 40 / 20s (50Hz) continue; if (!takeRun("drs-pedestal", 1000)) // 40 / 20s (50Hz) continue; dim.send("FAD_CONTROL/SET_FILE_FORMAT", 2); if (!takeRun("drs-pedestal", 1000)) // 40 / 20s (50Hz) continue; if (!takeRun("drs-time", 1000)) // 40 / 20s (50Hz) continue; dim.send("FAD_CONTROL/RESET_SECONDARY_DRS_BASELINE"); if (!takeRun("pedestal", 1000)) // 40 / 10s (80Hz) continue; dim.send("FAD_CONTROL/SET_FILE_FORMAT", 2); if (!takeRun("pedestal", 1000)) // 40 / 10s (80Hz) continue; // ----------- // 4'40 / 2'00 break; } dim.send("RATE_CONTROL/STOP"); // get out of GlobalThresholdSet console.out(" DRS cal: end"); } // ---------------------------------------------------------------- function OpenLid() { var horizon_parameter = "nautical"; while (Sun.horizon( horizon_parameter ).isUp) { var minutes_until_sunset = (Sun.horizon( horizon_parameter ).set - new Date())/60000; console.out("Open Lid - Info: "); console.out(" Sun is above FACT-horizon, lid cannot be opened."); console.out(" sleeping " + minutes_until_sunset + "minutes ..."); v8.sleep(60000); } console.out("Open lid: start"); // Wait for lid to be open if (dim.state("LID_CONTROL").name=="Closed") dim.send("LID_CONTROL/OPEN"); dim.wait("LID_CONTROL", "Open", 30000); console.out("Open lid: done"); } function CloseLid() { console.out("Close lid: start"); // Wait for lid to be open if (dim.state("LID_CONTROL").name=="Open") dim.send("LID_CONTROL/CLOSE"); dim.wait("LID_CONTROL", "Closed", 30000); console.out("Close lid: end"); } // ---------------------------------------------------------------- var service_feedback = new Subscription("FEEDBACK/DEVIATION"); // DN: Why is voltageOff() implemented as // a method of a Subscription to a specific Service // I naively would think of voltageOff() as an unbound function. // I seems to me it has to be a method of a Subscription object, in order // to use the update counting method. But does it have to be // a Subscription to FEEDBACK/DEVIATION, or could it work with other services as well? service_feedback.voltageOff = function() { console.out(" Voltage off: start"); var isOn = dim.state("BIAS_CONTROL").name=="VoltageOn"; if (isOn) { console.out(" Voltage on: switch off"); dim.send("BIAS_CONTROL/SET_ZERO_VOLTAGE"); } dim.wait("BIAS_CONTROL", "VoltageOff", 5000); // FEEDBACK stays in CurrentCtrl when Voltage is off but output enabled // dim.wait("FEEDBACK", "CurrentCtrlIdle", 1000); console.out(" Voltage off: end"); } // DN: The name of the method voltageOn() in the context of the method // voltageOff() is a little bit misleading, since when voltageOff() returns // the caller can be sure the voltage is off, but when voltageOn() return // this is not the case, in the sense, that the caller can now take data. // instead the caller of voltageOn() *must* call waitForVoltageOn() afterwards // in order to safely take good-quality data. // This could lead to nasty bugs in the sense, that the second call might // be forgotten by somebody // // so I suggest to rename voltageOn() --> prepareVoltageOn() // waitForVoltageOn() stays as it is // and one creates a third method called:voltageOn() like this /* service_feedback.voltageOn = function() * { * this.prepareVoltageOn(); * this.waitForVoltageOn(); * } * * */ // For convenience. service_feedback.voltageOn = function() { //if (Sun.horizon("FACT").isUp) // throw new Error("Sun is above FACT-horizon, voltage cannot be switched on."); console.out(" Voltage on: start"); var isOff = dim.state("BIAS_CONTROL").name=="VoltageOff"; if (isOff) { console.out(" Voltage on: switch on"); console.out(JSON.stringify(dim.state("BIAS_CONTROL"))); dim.send("BIAS_CONTROL/SET_GLOBAL_DAC", 1); } // Wait until voltage on dim.wait("BIAS_CONTROL", "VoltageOn", 5000); // From now on the feedback waits for a valid report from the FSC // and than switchs to CurrentControl dim.wait("FEEDBACK", "CurrentControl", 60000); if (isOff) { this.cnt = this.get().counter; console.out(" Voltage on: cnt="+this.cnt); } console.out(" Voltage on: end"); } service_feedback.waitForVoltageOn = function() { // waiting 45sec for the current control to stabilize... // v8.sleep(45000); // ----- Wait for at least three updates ----- // The feedback is started as if the camera where at 0deg // Then after the first temp update, the temperature will be set to the // correct value (this has already happened) // So we only have to wait for the current to get stable. // This should happen after three to five current updates. // So we want one recent temperature update // and three recent current updates console.out(" Voltage wait: start"); while (this.cnt==undefined || this.get().counter<=this.cnt+2) v8.sleep(); console.out(" Voltage wait: end [cnt="+this.get().counter+"]"); } // ================================================================ // Crosscheck all states // ================================================================ include('scripts/Startup.js');//Startup(); /* include('scripts/CheckStates.js'); var table = [ [ "TNG_WEATHER" ], [ "MAGIC_WEATHER" ], [ "CHAT" ], [ "SMART_FACT" ], [ "FSC_CONTROL", [ "Connected" ] ], [ "MCP", [ "Idle" ] ], [ "TIME_CHECK", [ "Valid" ] ], [ "PWR_CONTROL", [ "SystemOn" ] ], [ "AGILENT_CONTROL", [ "VoltageOn" ] ], [ "BIAS_CONTROL", [ "VoltageOff" ] ], [ "FEEDBACK", [ "CurrentControl", "CurrentCtrlIdle", "Connected" ] ], [ "RATE_SCAN", [ "Connected" ] ], [ "RATE_CONTROL", [ "Connected" ] ], [ "LID_CONTROL", [ "Open", "Closed" ] ], [ "DRIVE_CONTROL", [ "Armed", "Tracking", "OnTrack" ] ], [ "FTM_CONTROL", [ "Idle", "TriggerOn" ] ], [ "FAD_CONTROL", [ "Connected", "WritingData" ] ], [ "DATA_LOGGER", [ "NightlyFileOpen", "WaitForRun", "Logging" ] ], ]; console.out("Checking states."); if (!checkStates(table, 10000)) { console.out("Something unexpected has happened. Although the startup-", "procedure has finished, not all servers are in the state", "in which they ought to be. Please, try to find out what", "happened..."); exit(); } console.out("Checking states: done."); */ // ---------------------------------------------------------------- console.out("Checking send."); checkSend(["MCP", "DRIVE_CONTROL", "LID_CONTROL", "FAD_CONTROL", "FEEDBACK"]); console.out("Checking send: done"); // ---------------------------------------------------------------- console.out("Feedback init: start."); service_feedback.get(5000); dim.send("FEEDBACK/ENABLE_OUTPUT", true); dim.send("FEEDBACK/START_CURRENT_CONTROL", 0.); console.out("Feedback init: end."); // ---------------------------------------------------------------- // ================================================================ // ---------------------------------------------------------------- // this file just contains the definition of // the variable observations, which builds our nightly schedule, hence the filename include('scripts/schedule.js'); // make Observation objects from user input and check if 'date' is increasing. for (var i=0; i0 && observations[i].start <= observations[i-1].start) { throw new Error("Start time '"+ observations[i].start.toUTCString()+ "' in row "+i+" exceeds start time in row "+(i-1)); } } // ---------------------------------------------------------------- // Bring the system into a well defined state // ---------------------------------------------------------------- console.out("Drs runs init: start."); // FIMXE: Double subscription is not allowed! // also Startup needs DRS_RUNS var service_drs = new Subscription("FAD_CONTROL/DRS_RUNS"); service_drs.get(5000, false); console.out("Drs runs init: end."); // FIXME: Check if the last DRS calibration was complete? // ---------------------------------------------------------------- // We have to stup a few things here which might not by set by Startup.js dim.send("FAD_CONTROL/SET_FILE_FORMAT", 2); // ---------------------------------------------------------------- console.out("Start main loop."); function Startup() { /**** dummy ****/ console.out(" => [STARTUP] called."); } function Shutdown() { /**** dummy ****/ //console.out(" => [SHUTDOWN] called."); console.out("Shutdown: start"); service_feedback.voltageOff(); CloseLid(); dim.send("DRIVE_CONTROL/PARK"); dim.wait("DRIVE_CONTROL", "Moving", 3000); dim.wait("DRIVE_CONTROL", "Armed", 120000); console.out("Shutdown: end"); } // Get the observation scheduled for 'now' from the table and // return its index function getObservation(now) { if (now==undefined) now = new Date(); if (isNaN(now.valueOf())) throw new Error("Date argument in getObservation invalid."); for (var i=0; i Bias On/Off?, Lid open/closed? CloseLid(); } run = 0; } lastObs = idxObs; // We have performed startup or shutdown... wait for next observation if (run==-1) { v8.sleep(1000); continue; } // Check if obs.task is one of the one-time-tasks switch (obs.task) { case "STARTUP": console.out(" STARTUP", ""); Startup(); // BiasOn/Off?, Lid open/close? CloseLid(); console.out(" Take DRS calibration."); doDrsCalibration(); // -> VoltageOff service_feedback.voltageOn(); service_feedback.waitForVoltageOn(); // Before we can switch to 3000 we have to make the right DRS calibration console.out(" Take single p.e. run."); while (!takeRun("pedestal", 5000)); service_feedback.voltageOff(); console.out(" Waiting for first scheduled observation.",""); run = -1; continue; case "SHUTDOWN": console.out(" SHUTDOWN",""); Shutdown(); console.out(" Waiting for next startup.", ""); run = -1; continue; case "RATESCAN": console.out(" RATESCAN "); dim.send("DRIVE_CONTROL/STOP"); dim.wait("DRIVE_CONTROL", "Armed", 3000); if (obs.source != undefined) dim.send("DRIVE_CONTROL/TRACK_ON", obs.source); else dim.send("DRIVE_CONTROL/TRACK", obs.ra, obs.dec); //OpenLid(); dim.wait("DRIVE_CONTROL", "OnTrack", 300000); // Checking if system is Ready for Data Taking, which is in this case // the same as Ready for RateScan. // // this part might be simply wrong here, since I should be able to expect // the system to be able for data taking. And if not, then it is not here, // to bring the system into a better state, correct? dim.wait("FEEDBACK", "CurrentControl", -100); dim.wait("BIAS_CONTROL", "VoltageOn", -100); dim.wait("FAD_CONTROL", "Connected", -100); dim.wait("RATE_SCAN","Connected", 5000); dim.send("RATE_SCAN/START_THRESHOLD_SCAN", 50, 1000, -10); // lets wait if the Ratescan really starts .. it should be started after 10sec max. dim.wait("RATE_SCAN", "InProgress", 10000); dim.wait("RATE_SCAN", "Connected", 2700000); // this line is actually some kind of hack. // after the Ratescan, no data is written to disk. I don't know why, but it happens all the time // So I decided to put this line here as a kind of patchwork.... dim.send("FAD_CONTROL/SET_FILE_FORMAT", 2); console.out("Ratescan done."); run = -1; continue; } /* if (Sun.horizon("FACT").isUp) { console.out(" SHUTDOWN",""); Shutdown(); console.out(" Exit forced due to broken schedule", ""); exit(); } */ // Calculate remaining time for this observation in minutes var remaining = (nextObs.start-new Date())/60000; // ------------------------------------------------------------ console.out(" Checking states [mainloop]"); var table = [ [ "TNG_WEATHER" ], [ "MAGIC_WEATHER" ], [ "CHAT" ], [ "SMART_FACT" ], [ "DATA_LOGGER", [ "NightlyFileOpen", "WaitForRun", "Logging" ] ], [ "FSC_CONTROL", [ "Connected" ] ], [ "MCP", [ "Idle" ] ], [ "TIME_CHECK", [ "Valid" ] ], [ "PWR_CONTROL", [ "SystemOn" ] ], [ "AGILENT_CONTROL", [ "VoltageOn" ] ], [ "BIAS_CONTROL", [ "VoltageOff", "VoltageOn" ] ], [ "FEEDBACK", [ "CurrentCtrlIdle", "CurrentControl" ] ], [ "RATE_SCAN", [ "Connected" ] ], [ "RATE_CONTROL", [ "Connected", "InProgress" ] ], [ "LID_CONTROL", [ "Open", "Closed" ] ], [ "DRIVE_CONTROL", [ "Armed", "Tracking", "OnTrack" ] ], [ "FTM_CONTROL", [ "Idle", "TriggerOn" ] ], [ "FAD_CONTROL", [ "Connected", "WritingData" ] ], ]; if (!checkStates(table)) { throw new Error("Something unexpected has happened. One of the servers"+ "is in a state in which it should not be. Please,"+ "try to find out what happened..."); } console.out(" Checking states: end."); // ------------------------------------------------------------ console.out(" Run #"+run+" ("+parseInt(remaining)+"min)"); // ----- Time since last DRS Calibration [min] ------ var runs = service_drs.get(0, false); var diff = (new Date()-runs.time)/60000; // Warning: 'roi=300' is a number which is not intrisically fixed // but can change depending on the taste of the observers var valid = runs.data[1][2]>0 && runs.data[0]==300; if (valid) console.out(" Last DRS calib: "+diff+"min ago"); else console.out(" No valid drs calibration"); // Changine pointing position and take calibration... // ...every four runs (every ~20min) // ...if at least ten minutes of observation time are left // ...if this is the first run on the source var point = (run%4==0 && remaining>10) || run==0; // Take DRS Calib... // ...every four runs (every ~20min) // ...at last every two hours // ...when DRS temperature has changed by more than 2deg (?) // ...when more than 15min of observation are left // ...no drs calibration was done yet var drscal = (run%4==0 && (remaining>15 && diff>70)) || !valid; if (point) { var wobble = parseInt(run/4)%2; //console.out(" Move telescope to '"+source+"' "+offset+" "+wobble); console.out(" Move telescope to '"+obs.source+"' ["+wobble+"]"); //var offset = observations[obs][2]; //var wobble = observations[obs][3 + parseInt(run/4)%2]; //dim.send("DRIVE_CONTROL/TRACK_SOURCE", offset, wobble, source); dim.send("DRIVE_CONTROL/TRACK_WOBBLE", wobble+1, obs.source); // Do we have to check if the telescope is really moving? // We can cross-check the SOURCE service later } if (drscal) { console.out(" Take DRS calibration."); doDrsCalibration(); // -> VoltageOff } OpenLid(); // voltage must be switched on after the lid is open for the // feedback to adapt the voltage properly to the night-sky // background light level. service_feedback.voltageOn(); // This is now th right time to wait for th drive to be stable dim.wait("DRIVE_CONTROL", "OnTrack", 150000); // 110s for turning and 30s for stabilizing // Now we have to be prepared for data-taking: // make sure voltage is on service_feedback.waitForVoltageOn(); // If pointing had changed, do calibration if (point) { console.out(" Calibration."); // Calibration (2% of 20') while (1) { if (!takeRun("pedestal", 1000)) // 80 Hz -> 10s continue; if (!takeRun("light-pulser-ext", 1000)) // 80 Hz -> 10s continue; break; } } console.out(" Taking data: start [5min]"); var len = 300; while (len>0) { var time = new Date(); if (!takeRun("data", -1, len)) // Take data (5min) len -= parseInt((new Date()-time)/1000); else break; } //v8.sleep(360000); console.out(" Taking data: done"); run++; } service_drs.close();