/** * @fileOverview This file has functions related to documenting JavaScript. * @author Thomas Bretz */ 'use strict'; dim.log("Start: "+__FILE__+" ["+__DATE__+"]"); // This should be set in dimctrl.rc as JavaScript.schedule-database. // It is sent together with the script to the dimserver. // If started directly, it has to be set after the command: // // .js scripts/Main.js schedule-database=... // if (!$['schedule-database']) throw new Error("Environment 'schedule-database' not set!"); //dimctrl.defineState(37, "TimeOutBeforeTakingData", "MCP took more than 5minutes to start TakingData"); // ================================================================ // Code related to the schedule // ================================================================ //this is just the class implementation of 'Observation' include('scripts/Observation_class.js'); include('scripts/getSchedule.js'); var observations = [ ]; // 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."); observations = getSchedule(); if (observations.length==0) return -1; var suspended = -1; var rc = observations.length-1; for (var i=0; inow) { rc = i-1; break; } // If the loop just passed a suspend, i.e. it is in the past, set the suspend flag if (observations[i][0].task=="SUSPEND") suspended = i; // If the loop just passed a resume, i.e. it is in the past, remove the suspend flag if (observations[i][0].task=="RESUME") suspended = -1; } // Now rc contains the index of the first observation in the past // If the system is in suspend mode, the suspend observation is // returned as recent measurement. For convenience (to avoid that // the next observation is announced), all future observations // are removed. if (suspended>=0) { observations.splice(suspended+1); return suspended; } // Observations have already been resumed and the last scheduled // observation is the resume itself: remove the resume and // all leading suspend/resume pairs until the last observation // is found while (rc>=0 && observations[rc][0].task=="RESUME") { observations.splice(rc--, 1); // Find previous suspend if (rc>=0 && observations[rc][0].task=="SUSPEND") observations.splice(rc--, 1); } return rc; } // ================================================================ // Code to check whether observation is allowed // ================================================================ /* 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; } */ // ================================================================ // Code to perform the DRS calib sequence // ================================================================ var irq; function doDrsCalibration(where) { dim.log("Starting DRS calibration ["+where+"]"); service_feedback.voltageOff(); var tm = new Date(); while (!irq) { dim.send("FAD_CONTROL/START_DRS_CALIBRATION"); if (irq || !takeRun("drs-pedestal", 1000)) // 40 / 20s (50Hz) continue; if (irq || !takeRun("drs-gain", 1000)) // 40 / 20s (50Hz) continue; if (where!="data") { if (irq || !takeRun("drs-pedestal", 1000)) // 40 / 20s (50Hz) continue; } break; } if (where!="data") { dim.send("FAD_CONTROL/SET_FILE_FORMAT", 6); while (!irq && !takeRun("drs-pedestal", 1000)); // 40 / 20s (50Hz) while (!irq && !takeRun("drs-time", 1000)); // 40 / 20s (50Hz) } while (!irq) { dim.send("FAD_CONTROL/RESET_SECONDARY_DRS_BASELINE"); if (takeRun("pedestal", 1000)) // 40 / 10s (80Hz) break; } dim.send("FAD_CONTROL/SET_FILE_FORMAT", 6); while (!irq && !takeRun("pedestal", 1000)); // 40 / 10s (80Hz) // ----------- // 4'40 / 2'00 if (irq) dim.log("DRS calibration interrupted [%.1fs]".$((new Date()-tm)/1000)); else dim.log("DRS calibration done [%.1fs]".$((new Date()-tm)/1000)); } // ================================================================ // Code related to the lid // ================================================================ function OpenLid() { /* while (Sun.horizon(-13).isUp) { var now = new Date(); var minutes_until_sunset = (Sun.horizon(-13).set - now)/60000; console.out(now.toUTCString()+": Sun above FACT-horizon, lid cannot be opened: sleeping 1min, remaining %.1fmin".$(minutes_until_sunset)); v8.sleep(60000); }*/ var isClosed = dim.state("LID_CONTROL").name=="Closed"; var isInconsistent = dim.state("LID_CONTROL").name=="Inconsistent"; var tm = new Date(); // Wait for lid to be open if (isClosed || isInconsistent) { dim.log("Opening lid"); dim.send("LID_CONTROL/OPEN"); dim.log("Turning off IR camera LEDs..."); var cam = new Curl("fact@cam/cgi-bin/user/Config.cgi"); cam.data.push("action=set"); cam.data.push("Camera.System.Title=Camera1"); cam.data.push("Camera.General.IRControl.Value=2"); cam.data.push("Camera.System.Display=ALL"); cam.data.push("Camera.Environment=OUTDOOR"); var ret = cam.send(); dim.log("Camera response: "+ret.data.replace(/\n/g,"/")+" ["+ret.rc+"]"); } dim.wait("LID_CONTROL", "Open", 30000); if (isClosed || isInconsistent) dim.log("Lid open [%.1fs]".$((new Date()-tm)/1000)); } function CloseLid() { var isOpen = dim.state("LID_CONTROL").name=="Open"; var tm = new Date(); // Wait for lid to be open if (isOpen) { if (dim.state("FTM_CONTROL").name=="TriggerOn") { dim.send("FTM_CONTROL/STOP_TRIGGER"); dim.wait("FTM_CONTROL", "Valid", 3000); } dim.log("Closing lid."); dim.send("LID_CONTROL/CLOSE"); } v8.timeout(30000, function() { if (dim.state("LID_CONTROL").name=="Closed" || dim.state("LID_CONTROL").name=="Inconsistent") return true; }); //dim.wait("LID_CONTROL", "Closed", 30000); //dim.wait("LID_CONTROL", "Inconsistent", 30000); if (isOpen) dim.log("Lid closed [%.1fs]".$((new Date()-tm)/1000)); } // ================================================================ // Interrupt data taking in case of high currents // ================================================================ dim.onchange['FEEDBACK'] = function(state) { if ((state.name=="Critical" || state.name=="OnStandby") && (this.prev!="Critical" && this.prev!="OnStandby")) { console.out("Feedback state changed from "+this.prev+" to "+state.name+" [Main.js]"); irq = "RESCHEDULE"; } this.prev=state.name; } // ================================================================ // Code related to switching bias voltage on and off // ================================================================ var service_feedback = new Subscription("FEEDBACK/CALIBRATED_CURRENTS"); service_feedback.onchange = function(evt) { if (!evt.data) return; if (this.ok==undefined) return; var Unom = evt.obj['U_nom']; var Uov = evt.obj['U_ov']; if (!Uov) return; var cnt = 0; var avg = 0; for (var i=0; i<320; i++) { // This is a fix for the channel with a shortcut if (i==272) continue; var dU = Uov[i]-Unom; // 0.022 corresponds to 1 DAC count (90V/4096) if (Math.abs(dU)>0.033) cnt++; avg += dU; } avg /= 320; this.ok = cnt<3;// || (this.last!=undefined && Math.abs(this.last-avg)<0.002); console.out(" DeltaUov=%.3f (%.3f) [N(>0.033V)=%d]".$(avg, avg-this.last, cnt)); this.last = avg; } service_feedback.voltageOff = function() { var state = dim.state("BIAS_CONTROL").name; if (state=="Disconnected") { console.out(" Voltage off: bias crate disconnected!"); return; } // check of feedback has to be switched on var isOn = state=="VoltageOn" || state=="Ramping"; if (isOn) { dim.log("Switching voltage off."); if (dim.state("FTM_CONTROL").name=="TriggerOn") { dim.send("FTM_CONTROL/STOP_TRIGGER"); dim.wait("FTM_CONTROL", "Valid", 3000); } // Supress the possibility that the bias control is // ramping and will reject the command to switch the // voltage off //dim.send("FEEDBACK/STOP"); //dim.wait("FEEDBACK", "Calibrated", 3000); // Make sure we are not in Ramping anymore //dim.wait("BIAS_CONTROL", "VoltageOn", 3000); // Switch voltage off dim.send("BIAS_CONTROL/SET_ZERO_VOLTAGE"); } dim.wait("BIAS_CONTROL", "VoltageOff", 60000); // FIXME: 30000? dim.wait("FEEDBACK", "Calibrated", 3000); // FEEDBACK stays in CurrentCtrl when Voltage is off but output enabled // dim.wait("FEEDBACK", "CurrentCtrlIdle", 1000); if (isOn) dim.log("Voltage off."); } // 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(ov) { if (isNaN(ov)) ov = 1.1; if (this.ov!=ov && dim.state("FEEDBACK").name=="InProgress") // FIXME: Warning, OnStandby, Critical if (ov8 ? dim.wait("FEEDBACK", "InProgress", 45000); this.ov = ov; } // Wait until voltage on dim.wait("BIAS_CONTROL", "VoltageOn", 60000); // FIXME: 30000? } service_feedback.waitForVoltageOn = function() { // Avoid output if condition is already fulfilled dim.log("Waiting for voltage to be stable."); function func() { if (irq || this.ok==true) return true; } var now = new Date(); this.last = undefined; this.ok = false; v8.timeout(4*60000, func, this); // FIMXE: Remove 4! this.ok = undefined; if (irq) dim.log("Waiting for stable voltage interrupted."); else dim.log("Voltage stable within limits"); } // ================================================================ // Function to shutdown the system // ================================================================ function Shutdown(type) { if (!type) type = "default"; dim.log("Starting shutdown ["+type+"]."); var now1 = new Date(); var bias = dim.state("BIAS_CONTROL").name; if (bias=="VoltageOn" || bias=="Ramping") service_feedback.voltageOn(0); CloseLid(); var now2 = new Date(); dim.send("DRIVE_CONTROL/PARK"); console.out("","Waiting for telescope to park. This may take a while."); // FIXME: This might not work is the drive is already close to park position //dim.wait("DRIVE_CONTROL", "Parking", 3000); /* // Check if DRS calibration is necessary var diff = getTimeSinceLastDrsCalib(); if (diff>30 || diff==null) { doDrsCalibration("singlepe"); // will turn voltage off if (irq) break; }*/ //take single pe run if required if (type=="singlepe") { dim.log("Taking single-pe run."); // The voltage must be on service_feedback.voltageOn(); service_feedback.waitForVoltageOn(); // Before we can switch to 3000 we have to make the right DRS calibration dim.log("Taking single p.e. run."); while (!irq && !takeRun("single-pe", 10000)); /* Maybe we need to send a trigger... but data runs contain pedestal triggers... so it should work in any case... var customRun = function() { v8.sleep(500);//wait that configuration is set dim.wait("FTM_CONTROL", "TriggerOn", 15000); dim.send("FAD_CONTROL/SEND_SINGLE_TRIGGER"); dim.send("RATE_CONTROL/STOP"); dim.send("FTM_CONTROL/STOP_TRIGGER"); dim.wait("FTM_CONTROL", "Valid", 3000); dim.send("FTM_CONTROL/ENABLE_TRIGGER", true); dim.send("FTM_CONTROL/SET_TIME_MARKER_DELAY", 123); dim.send("FTM_CONTROL/SET_THRESHOLD", -1, obs[sub].threshold); v8.sleep(500);//wait that configuration is set dim.send("FTM_CONTROL/START_TRIGGER"); dim.wait("FTM_CONTROL", "TriggerOn", 15000); }*/ } //wait until drive is in locked (after it reached park position) dim.wait("DRIVE_CONTROL", "Locked", 150000); //unlock drive if task was sleep if (type=="unlock") dim.send("DRIVE_CONTROL/UNLOCK"); // It is unclear what comes next, so we better switch off the voltage service_feedback.voltageOff(); dim.log("Finishing shutdown."); var now3 = new Date(); dim.send("FTM_CONTROL/STOP_TRIGGER"); dim.wait("FTM_CONTROL", "Valid", 3000); if (bias!="Disconnected") dim.wait("FEEDBACK", "Calibrated", 3000); if (type!="unlock") { dim.send("BIAS_CONTROL/DISCONNECT"); var pwrctrl_state = dim.state("PWR_CONTROL").name; if (pwrctrl_state=="SystemOn" || pwrctrl_state=="BiasOff" || pwrctrl_state=="DriveOn") dim.send("PWR_CONTROL/TOGGLE_DRIVE"); dim.wait("BIAS_CONTROL", "Disconnected", 3000); dim.wait("PWR_CONTROL", "DriveOff", 6000); } var sub = new Subscription("DRIVE_CONTROL/POINTING_POSITION"); sub.get(5000); // FIXME: Proper error message in case of failure var report = sub.get(); console.out(""); console.out("Shutdown procedure ["+type+"] seems to be finished..."); console.out(" "+new Date().toUTCString()); console.out(" Telescope at Zd=%.1fdeg Az=%.1fdeg".$(report.obj['Zd'], report.obj['Az'])); console.out(" Please check on the web cam that the park position was reached"); console.out(" and the telescope is not moving anymore."); console.out(" Please check visually that the lid is really closed and"); console.out(" that the biasctrl really switched the voltage off.", ""); console.out(" DRIVE_CONTROL: "+dim.state("DRIVE_CONTROL").name); console.out(" FEEDBACK: "+dim.state("FEEDBACK").name); console.out(" FTM_CONTROL: "+dim.state("FTM_CONTROL").name); console.out(" BIAS_CONTROL: "+dim.state("BIAS_CONTROL").name); console.out(" PWR_CONTROL: "+dim.state("PWR_CONTROL").name); console.out(""); dim.log("Shutdown: end ["+(now2-now1)/1000+"s, "+(now3-now2)/1000+"s, "+(new Date()-now3)/1000+"s]"); console.out(""); sub.close(); } // ================================================================ // Function to set the system to sleep-mode // ================================================================ // FIXME: do not repeat code from shutdown-function /* function GoToSleep() { CloseLid(); var isArmed = dim.state("DRIVE_CONTROL").name=="Armed"; if (!isArmed) { dim.log("Drive not ready to move. -> send STOP"); dim.send("DRIVE_CONTROL/STOP"); dim.wait("DRIVE_CONTROL", "Armed", 5000); } dim.send("DRIVE_CONTROL/MOVE_TO 101 0");//park position var sub = new Subscription("DRIVE_CONTROL/POINTING_POSITION"); sub.get(5000); // FIXME: Proper error message in case of failure function func() { var report = sub.get(); var zd = report.obj['Zd']; var az = report.obj['Az']; if (zd>100 && Math.abs(az)<1) return true; return undefined; } try { v8.timeout(150000, func); } catch (e) { var p = sub.get(); dim.log('Park position not reached? Telescope at Zd='+p.obj['Zd']+' Az='+p.obj['Az']); } var p2 = sub.get(); dim.log('Telescope at Zd=%.1fdeg Az=%.1fdeg'.$(p2.obj['Zd'], p2.obj['Az'])); sub.close(); } */ // ================================================================ // Check datalogger subscriptions // ================================================================ var datalogger_subscriptions = new Subscription("DATA_LOGGER/SUBSCRIPTIONS"); datalogger_subscriptions.get(3000, false); datalogger_subscriptions.check = function() { var obj = this.get(); if (!obj.data) throw new Error("DATA_LOGGER/SUBSCRIPTIONS not available."); var expected = [ "AGILENT_CONTROL_24V/DATA", "AGILENT_CONTROL_50V/DATA", "AGILENT_CONTROL_80V/DATA", "BIAS_CONTROL/CURRENT", "BIAS_CONTROL/DAC", "BIAS_CONTROL/NOMINAL", "BIAS_CONTROL/VOLTAGE", "DRIVE_CONTROL/POINTING_POSITION", "DRIVE_CONTROL/SOURCE_POSITION", "DRIVE_CONTROL/STATUS", "DRIVE_CONTROL/TRACKING_POSITION", "FAD_CONTROL/CONNECTIONS", "FAD_CONTROL/DAC", "FAD_CONTROL/DNA", "FAD_CONTROL/DRS_RUNS", "FAD_CONTROL/EVENTS", "FAD_CONTROL/FEEDBACK_DATA", "FAD_CONTROL/FILE_FORMAT", "FAD_CONTROL/FIRMWARE_VERSION", "FAD_CONTROL/INCOMPLETE", "FAD_CONTROL/PRESCALER", "FAD_CONTROL/REFERENCE_CLOCK", "FAD_CONTROL/REGION_OF_INTEREST", "FAD_CONTROL/RUNS", "FAD_CONTROL/RUN_NUMBER", "FAD_CONTROL/START_RUN", "FAD_CONTROL/STATISTICS1", "FAD_CONTROL/STATS", "FAD_CONTROL/STATUS", "FAD_CONTROL/TEMPERATURE", "FEEDBACK/CALIBRATED_CURRENTS", "FEEDBACK/CALIBRATION", "FEEDBACK/CALIBRATION_R8", "FEEDBACK/CALIBRATION_STEPS", /* "FEEDBACK/REFERENCE",*/ "FSC_CONTROL/CURRENT", "FSC_CONTROL/HUMIDITY", "FSC_CONTROL/TEMPERATURE", "FSC_CONTROL/VOLTAGE", "FTM_CONTROL/COUNTER", "FTM_CONTROL/DYNAMIC_DATA", "FTM_CONTROL/ERROR", "FTM_CONTROL/FTU_LIST", "FTM_CONTROL/PASSPORT", "FTM_CONTROL/STATIC_DATA", "FTM_CONTROL/TRIGGER_RATES", "GPS_CONTROL/NEMA", "SQM_CONTROL/DATA", //"LID_CONTROL/DATA", "LID_CONTROL/MOTORS", "MAGIC_LIDAR/DATA", "MAGIC_WEATHER/DATA", "RAIN_SENSOR/DATA", "MCP/CONFIGURATION", "PWR_CONTROL/DATA", "RATE_CONTROL/THRESHOLD", "RATE_SCAN/DATA", "RATE_SCAN/PROCESS_DATA", "TEMPERATURE/DATA", "TIME_CHECK/OFFSET", "TNG_WEATHER/DATA", "TNG_WEATHER/DUST", "PFMINI_CONTROL/DATA", "BIAS_TEMP/DATA", ]; function map(entry) { if (entry.length==0) return undefined; var rc = entry.split(','); if (rc.length!=2) throw new Error("Subscription list entry '"+entry+"' has wrong number of elements."); return rc; } var list = obj.data.split('\n').map(map); function check(name) { if (list.every(function(el){return el==undefined || el[0]!=name;})) throw new Error("Subscription to '"+name+"' not available."); } expected.forEach(check); } // ================================================================ // Crosscheck all states // ================================================================ // ---------------------------------------------------------------- // Do a standard startup to bring the system in into a well // defined state // ---------------------------------------------------------------- include('scripts/Startup.js'); // ================================================================ // Code to monitor clock conditioner // ================================================================ var sub_counter = new Subscription("FTM_CONTROL/COUNTER"); sub_counter.onchange = function(evt) { if (evt.qos>0 && evt.qos!=2 && evt.qos&0x100==0) throw new Error("FTM reports: clock conditioner not locked."); } // ================================================================ // Code related to monitoring the fad system // ================================================================ // This code is here, because scripts/Startup.js needs the // same subscriptions... to be revised. var sub_incomplete = new Subscription("FAD_CONTROL/INCOMPLETE"); var sub_connections = new Subscription("FAD_CONTROL/CONNECTIONS"); var sub_startrun = new Subscription("FAD_CONTROL/START_RUN"); sub_startrun.get(5000); include('scripts/takeRun.js'); // ---------------------------------------------------------------- // Check that everything we need is availabel to receive commands // (FIXME: Should that go to the general CheckState?) // ---------------------------------------------------------------- //console.out("Checking send."); checkSend(["MCP", "DRIVE_CONTROL", "LID_CONTROL", "FAD_CONTROL", "FEEDBACK"]); //console.out("Checking send: done"); // ---------------------------------------------------------------- // Bring feedback into the correct operational state // ---------------------------------------------------------------- //console.out("Feedback init: start."); service_feedback.get(5000); // ---------------------------------------------------------------- // Connect to the DRS_RUNS service // ---------------------------------------------------------------- //console.out("Drs runs init: start."); var sub_drsruns = new Subscription("FAD_CONTROL/DRS_RUNS"); sub_drsruns.get(5000); // FIXME: Check if the last DRS calibration was complete? function getTimeSinceLastDrsCalib() { // ----- Time since last DRS Calibration [min] ------ var runs = sub_drsruns.get(0); 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.obj['run'][2]>0 && runs.obj['roi']==300; if (valid) dim.log("Last DRS calibration was %.1fmin ago".$(diff)); else dim.log("No valid DRS calibration available."); return valid ? diff : null; } // ---------------------------------------------------------------- // Install interrupt handler // ---------------------------------------------------------------- var triggerReloadSources = false; function handleIrq(cmd, args, time, user) { console.out("Interrupt received:"); console.out(" IRQ: "+cmd); console.out(" Time: "+time); console.out(" User: "+user); if (cmd.toUpperCase()=="RELOADSOURCES") { dim.log("Reloading source table scheduled before next pointing operation."); triggerReloadSources = true; return; } irq = cmd ? cmd : "stop"; // This will end a run in progress as if it where correctly stopped if (dim.state("MCP").name=="TakingData") dim.send("MCP/STOP"); // This will stop a rate scan in progress if (dim.state("RATE_SCAN").name=="InProgress") dim.send("RATE_SCAN/STOP"); } dimctrl.setInterruptHandler(handleIrq); // ---------------------------------------------------------------- // Make sure we will write files // ---------------------------------------------------------------- dim.send("FAD_CONTROL/SET_FILE_FORMAT", 6); // ---------------------------------------------------------------- // Print some information for the user about the // expected first oberservation // ---------------------------------------------------------------- var test = getObservation(); if (test!=undefined) { var n = new Date(); if (observations.length>0 && test==-1) dim.log("First observation scheduled for "+observations[0].start.toUTCString()+" [id="+observations[0].id+"]"); if (test>=0 && test0 && observations[0].start>n+12*3600*1000) dim.log("No observations scheduled for the next 12 hours!"); if (observations.length==0) dim.log("No observations scheduled!"); } // ---------------------------------------------------------------- // Start main loop // ---------------------------------------------------------------- dim.log("Entering main loop."); console.out(""); var run = -2; // getObservation never called var sub; var lastId; var nextId; var sun = Sun.horizon(-12); var system_on; // undefined function processIrq() { if (!irq) return false; if (irq.toUpperCase()=="RESCHEDULE") { irq = undefined; return false; } if (irq.toUpperCase()=="OFF") { service_feedback.voltageOff(); dim.send("FAD_CONTROL/CLOSE_OPEN_FILES"); return true; } /* if (irq.toUpperCase()=="STOP") { dim.send("FAD_CONTROL/CLOSE_OPEN_FILES"); dim.send("MCP/STOP"); return true; }*/ if (irq.toUpperCase()=="SHUTDOWN") { Shutdown(); return true; } dim.log("IRQ "+irq+" unhandled... stopping script."); return true; } while (!processIrq()) { // Check if observation position is still valid // If source position has changed, set run=0 var idxObs = getObservation(); if (idxObs===undefined) break; // we are still waiting for the first observation in the schedule if (idxObs==-1) { // flag that the first observation will be in the future run = -1; v8.sleep(1000); continue; } // Check if we have to take action do to sun-rise var was_up = sun.isUp; sun = Sun.horizon(-12); if (!was_up && sun.isUp) { console.out(""); dim.log("Sun rise detected.... automatic shutdown initiated!"); // FIXME: State check? Shutdown(); system_on = false; continue; } // Current and next observation target var obs = observations[idxObs]; var nextObs = observations[idxObs+1]; // Check if observation target has changed if (lastId!=obs.id) // !Object.isEqual(obs, nextObs) { dim.log("Starting new observation ["+obs.start.toUTCString()+", id="+obs.id+"]"); // This is the first source, but we do not come from // a scheduled 'START', so we have to check if the // telescop is operational already sub = 0; if (run<0) { //Startup(); // -> Bias On/Off?, Lid open/closed? //CloseLid(); } // The first observation had a start-time in the past... // In this particular case start with the last entry // in the list of measurements if (run==-2) sub = obs.length-1; run = 0; lastId = obs.id; } //dim.log("DEBUG: Next observation scheduled for "+nextObs.start.toUTCString()+" [id="+nextObs.id+"]"); if (nextObs && nextId!=nextObs.id) { dim.log("Next observation scheduled for "+nextObs.start.toUTCString()+" [id="+nextObs.id+"]"); console.out(""); nextId = nextObs.id; } if (!nextObs && nextId) { if (obs[sub].task=="SUSPEND") dim.log("Further observation suspended."); else dim.log("No further observation scheduled."); console.out(""); nextId = undefined; } //if (nextObs==undefined && obs[obs.length-1].task!="SHUTDOWN") // throw Error("Last scheduled measurement must be a shutdown."); // We are done with all measurement slots for this // observation... wait for next observation if (sub>=obs.length) { v8.sleep(1000); continue; } if (system_on===false && obs[sub].task!="STARTUP") { v8.sleep(1000); continue; } // Check if sun is still up... only DATA and */ if ((obs[sub].task=="DATA" || obs[sub].task=="RATESCAN" || obs[sub].task=="RATESCAN2" ) && sun.isUp) { var now = new Date(); var remaining = (sun.set - now)/60000; console.out(now.toUTCString()+" - "+obs[sub].task+": Sun above FACT-horizon: sleeping 1min, remaining %.1fmin".$(remaining)); v8.sleep(60000); continue; } if (obs[sub].task!="IDLE" && (obs[sub].task!="DATA" && run>0)) dim.log("New task ["+obs[sub]+"]"); // FIXME: Maybe print a warning if Drive is on during day time! // It is not ideal that we allow the drive to be on during day time, but // otherwise it is difficult to allow e.g. the STARTUP at the beginning of the night var power_states = sun.isUp || !system_on ? [ "DriveOff", "SystemOn" ] : [ "SystemOn" ]; var drive_states = sun.isUp || !system_on ? undefined : [ "Initialized", "Tracking", "OnTrack" ]; // A scheduled task was found, lets check if all servers are // still only and in reasonable states. If this is not the case, // something unexpected must have happend and the script is aborted. //console.out(" Checking states [general]"); var table = [ [ "TNG_WEATHER" ], [ "MAGIC_WEATHER" ], [ "RAIN_SENSOR" ], [ "CHAT" ], [ "SMART_FACT" ], [ "TEMPERATURE" ], [ "DATA_LOGGER", [ "NightlyFileOpen", "WaitForRun", "Logging" ] ], [ "FSC_CONTROL", [ "Connected" ] ], [ "MCP", [ "Idle" ] ], [ "TIME_CHECK", [ "Valid" ] ], [ "PWR_CONTROL", power_states/*[ "SystemOn" ]*/ ], [ "AGILENT_CONTROL_24V", [ "VoltageOn" ] ], [ "AGILENT_CONTROL_50V", [ "VoltageOn" ] ], //[ "AGILENT_CONTROL_80V", [ "VoltageOn" ] ], [ "AGILENT_CONTROL_80V", [ "VoltageOn", "Disconnected" ] ], //hack to allow for data taking while agilent not available [ "BIAS_CONTROL", [ "VoltageOff", "VoltageOn", "Ramping" ] ], [ "FEEDBACK", [ "Calibrated", "InProgress", "OnStandby", "Warning", "Critical" ] ], [ "LID_CONTROL", [ "Open", "Closed" ] ], [ "DRIVE_CONTROL", drive_states/*[ "Armed", "Tracking", "OnTrack" ]*/ ], [ "FTM_CONTROL", [ "Valid", "TriggerOn" ] ], [ "FAD_CONTROL", [ "Connected", "RunInProgress" ] ], [ "RATE_SCAN", [ "Connected" ] ], [ "RATE_CONTROL", [ "Connected", "GlobalThresholdSet", "InProgress" ] ], [ "GPS_CONTROL", [ "Locked" ] ], [ "SQM_CONTROL", [ "Disconnected", "Connected", "Valid" ] ], [ "PFMINI_CONTROL", [ "Disconnected", "Connected", "Receiving" ] ], [ "BIAS_TEMP", [ "Valid" ] ], ]; 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..."); } datalogger_subscriptions.check(); // If this is an observation which needs the voltage to be swicthed on // skip that if the voltage is not stable /* if (obs[sub].task=="DATA" || obs[sub].task=="RATESCAN") { var state = dim.state("FEEDBACK").name; if (state=="Warning" || state=="Critical" || state=="OnStandby") { v8.sleep(1000); continue; } }*/ // Check if obs.task is one of the one-time-tasks switch (obs[sub].task) { case "IDLE": v8.sleep(5000); continue; case "SUSPEND": case "SLEEP": Shutdown("unlock"); //GoToSleep(); dim.log("Task finished ["+obs[sub].task+"]."); console.out(""); sub++; continue; case "STARTUP": CloseLid(); doDrsCalibration("startup"); // will switch the voltage off if (irq) break; service_feedback.voltageOn(); service_feedback.waitForVoltageOn(); // Before we can switch to 3000 we have to make the right DRS calibration dim.log("Taking single p.e. run."); while (!irq && !takeRun("single-pe", 10000)); // It is unclear what comes next, so we better switch off the voltage service_feedback.voltageOff(); system_on = true; dim.log("Task finished [STARTUP]"); console.out(""); break; case "SHUTDOWN": Shutdown("singlepe"); system_on = false; // FIXME: Avoid new observations after a shutdown until // the next startup (set run back to -2?) sub++; dim.log("Task finished [SHUTDOWN]"); console.out(""); //console.out(" Waiting for next startup.", ""); continue; case "DRSCALIB": doDrsCalibration("drscalib"); // will switch the voltage off dim.log("Task finished [DRSCALIB]"); console.out(""); break; case "SINGLEPE": // The lid must be closes CloseLid(); // Check if DRS calibration is necessary var diff = getTimeSinceLastDrsCalib(); if (diff>30 || diff==null) { doDrsCalibration("singlepe"); // will turn voltage off if (irq) break; } // The voltage must be on service_feedback.voltageOn(); service_feedback.waitForVoltageOn(); // Before we can switch to 3000 we have to make the right DRS calibration dim.log("Taking single p.e. run."); while (!irq && !takeRun("single-pe", 10000)); // It is unclear what comes next, so we better switch off the voltage service_feedback.voltageOff(); dim.log("Task finished [SINGLE-PE]"); console.out(""); break; case "OVTEST": var locked = dim.state("DRIVE_CONTROL").name=="Locked"; if (!locked) dim.send("DRIVE_CONTROL/PARK"); dim.send("FEEDBACK/STOP"); // The lid must be closed CloseLid(); if (!locked) { //console.out("Waiting for telescope to park. This may take a while."); dim.wait("DRIVE_CONTROL", "Locked", 3000); dim.send("DRIVE_CONTROL/UNLOCK"); } // Check if DRS calibration is necessary var diff = getTimeSinceLastDrsCalib(); if (diff>30 || diff==null) { doDrsCalibration("ovtest"); // will turn voltage off if (irq) break; } // The voltage must be on service_feedback.voltageOn(0.4); service_feedback.waitForVoltageOn(); dim.log("Taking single p.e. run (0.4V)"); while (!irq && !takeRun("single-pe", 10000)); for (var i=5; i<18 && !irq; i++) { dim.send("FEEDBACK/STOP"); dim.wait("FEEDBACK", "Calibrated", 3000); dim.wait("BIAS_CONTROL", "VoltageOn", 3000); dim.send("FEEDBACK/START", i*0.1); dim.wait("FEEDBACK", "InProgress", 45000); dim.wait("BIAS_CONTROL", "VoltageOn", 60000); // FIXME: 30000? service_feedback.waitForVoltageOn(); dim.log("Taking single p.e. run ("+(i*0.1)+"V)"); while (!irq && !takeRun("single-pe", 10000)); } // It is unclear what comes next, so we better switch off the voltage service_feedback.voltageOff(); dim.log("Task finished [OVTEST]"); console.out(""); break; case "RATESCAN": var tm1 = new Date(); // This is a workaround to make sure that we really catch // the new OnTrack state later and not the old one dim.send("DRIVE_CONTROL/STOP"); dim.wait("DRIVE_CONTROL", "Initialized", 15000); // The lid must be open OpenLid(); // Switch the voltage to a reduced level (Ubd) service_feedback.voltageOn(0); if (obs[sub].source != null) // undefined != null -> false { dim.log("Pointing telescope to '"+obs[sub].source+"'."); dim.send("DRIVE_CONTROL/TRACK_ON", obs[sub].source); } else { dim.log("Pointing telescope to ra="+obs[sub].ra+" dec="+obs[sub].dec); dim.send("DRIVE_CONTROL/TRACK", obs[sub].ra, obs[sub].dec); } dim.wait("DRIVE_CONTROL", "OnTrack", 150000); // 110s for turning and 30s for stabilizing // Now tracking stable, switch voltage to nominal level and wait // for stability. service_feedback.voltageOn(); service_feedback.waitForVoltageOn(); if (!irq) { dim.log("Starting calibration."); // Calibration (2% of 20') while (!irq) { if (irq || !takeRun("pedestal", 1000)) // 80 Hz -> 10s continue; //if (irq || !takeRun("light-pulser-ext", 1000)) // 80 Hz -> 10s // continue; break; } var tm2 = new Date(); dim.log("Starting ratescan."); //set reference to whole camera (in case it was changed) dim.send("RATE_SCAN/SET_REFERENCE_CAMERA"); // Start rate scan dim.send("RATE_SCAN/START_THRESHOLD_SCAN", 50, 1000, -10, "default"); // Lets wait if the ratescan really starts... this might take a few // seconds because RATE_SCAN configures the ftm and is waiting for // it to be configured. dim.wait("RATE_SCAN", "InProgress", 10000); dim.wait("RATE_SCAN", "Connected", 2700000); // Here one could implement a watchdog for the feedback as well, but what is the difference // whether finally one has to find out if the feedback was in the correct state // or the ratescan was interrupted? // 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", 6); dim.log("Ratescan done [%.1fs, %.1fs]".$((tm2-tm1)/1000, (new Date()-tm2)/1000)); } dim.log("Task finished [RATESCAN]"); console.out(""); break; // case "RATESCAN" case "RATESCAN2": var tm1 = new Date(); // This is a workaround to make sure that we really catch // the new OnTrack state later and not the old one dim.send("DRIVE_CONTROL/STOP"); dim.wait("DRIVE_CONTROL", "Initialized", 15000); if (obs[sub].rstype=="dark-bias-off") service_feedback.voltageOff(); else { // Switch the voltage to a reduced level (Ubd) var bias = dim.state("BIAS_CONTROL").name; if (bias=="VoltageOn" || bias=="Ramping") service_feedback.voltageOn(0); } // Open the lid if required if (!obs[sub].lidclosed) OpenLid(); else CloseLid(); // track source/position or move to position if (obs[sub].lidclosed) { dim.log("Moving telescope to zd="+obs[sub].zd+" az="+obs[sub].az); dim.send("DRIVE_CONTROL/MOVE_TO", obs[sub].zd, obs[sub].az); v8.sleep(3000); dim.wait("DRIVE_CONTROL", "Initialized", 150000); // 110s for turning and 30s for stabilizing } else { if (obs[sub].source != null) // undefined != null -> false { dim.log("Pointing telescope to '"+obs[sub].source+"'."); dim.send("DRIVE_CONTROL/TRACK_ON", obs[sub].source); } else { dim.log("Pointing telescope to ra="+obs[sub].ra+" dec="+obs[sub].dec); dim.send("DRIVE_CONTROL/TRACK", obs[sub].ra, obs[sub].dec); } dim.wait("DRIVE_CONTROL", "OnTrack", 150000); // 110s for turning and 30s for stabilizing } // Now tracking stable, switch voltage to nominal level and wait // for stability. if (obs[sub].rstype!="dark-bias-off") { service_feedback.voltageOn(); service_feedback.waitForVoltageOn(); } if (!irq) { var tm2 = new Date(); dim.log("Starting ratescan 2/1 ["+obs[sub].rstype+"]"); //set reference to whole camera (in case it was changed) dim.send("RATE_SCAN/SET_REFERENCE_CAMERA"); // Start rate scan dim.send("RATE_SCAN/START_THRESHOLD_SCAN", 50, 300, 20, obs[sub].rstype); // Lets wait if the ratescan really starts... this might take a few // seconds because RATE_SCAN configures the ftm and is waiting for // it to be configured. dim.wait("RATE_SCAN", "InProgress", 10000); //FIXME: discuss what best value is here dim.wait("RATE_SCAN", "Connected", 2700000);//45min //dim.wait("RATE_SCAN", "Connected", 1200000);//3.3h // Here one could implement a watchdog for the feedback as well, but what is the difference // whether finally one has to find out if the feedback was in the correct state // or the ratescan was interrupted? // 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", 6); dim.log("Ratescan 2/1 done [%.1fs, %.1fs]".$((tm2-tm1)/1000, (new Date()-tm2)/1000)); } if (!irq) { var tm2 = new Date(); dim.log("Starting ratescan 2/2 ["+obs[sub].rstype+"]"); // Start rate scan dim.send("RATE_SCAN/START_THRESHOLD_SCAN", 300, 1000, 100, obs[sub].rstype); // Lets wait if the ratescan really starts... this might take a few // seconds because RATE_SCAN configures the ftm and is waiting for // it to be configured. dim.wait("RATE_SCAN", "InProgress", 10000); dim.wait("RATE_SCAN", "Connected", 2700000); // Here one could implement a watchdog for the feedback as well, but what is the difference // whether finally one has to find out if the feedback was in the correct state // or the ratescan was interrupted? // 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", 6); dim.log("Ratescan 2/2 done [%.1fs, %.1fs]".$((tm2-tm1)/1000, (new Date()-tm2)/1000)); } dim.log("Task finished [RATESCAN2]"); console.out(""); break; // case "RATESCAN2" case "CUSTOM": // This is a workaround to make sure that we really catch // the new OnTrack state later and not the old one dim.send("DRIVE_CONTROL/STOP"); dim.wait("DRIVE_CONTROL", "Initialized", 15000); // Ramp bias if needed if (!obs[sub].biason) service_feedback.voltageOff(); else { // Switch the voltage to a reduced level (Ubd) var bias = dim.state("BIAS_CONTROL").name; if (bias=="VoltageOn" || bias=="Ramping") service_feedback.voltageOn(0); } // Close lid CloseLid(); // Move to position (zd/az) dim.log("Moving telescope to zd="+obs[sub].zd+" az="+obs[sub].az); dim.send("DRIVE_CONTROL/MOVE_TO", obs[sub].zd, obs[sub].az); v8.sleep(3000); dim.wait("DRIVE_CONTROL", "Initialized", 150000); // 110s for turning and 30s for stabilizing // Now tracking stable, switch voltage to nominal level and wait // for stability. if (obs[sub].biason) { service_feedback.voltageOn(); service_feedback.waitForVoltageOn(); } if (!irq) { dim.log("Taking custom run with time "+obs[sub].time+"s, threshold="+obs[sub].threshold+", biason="+obs[sub].biason); var customRun = function() { v8.sleep(500);//wait that configuration is set dim.wait("FTM_CONTROL", "TriggerOn", 15000); dim.send("FAD_CONTROL/SEND_SINGLE_TRIGGER"); dim.send("RATE_CONTROL/STOP"); dim.send("FTM_CONTROL/STOP_TRIGGER"); dim.wait("FTM_CONTROL", "Valid", 3000); dim.send("FTM_CONTROL/ENABLE_TRIGGER", true); dim.send("FTM_CONTROL/SET_TIME_MARKER_DELAY", 123); dim.send("FTM_CONTROL/SET_THRESHOLD", -1, obs[sub].threshold); v8.sleep(500);//wait that configuration is set dim.send("FTM_CONTROL/START_TRIGGER"); dim.wait("FTM_CONTROL", "TriggerOn", 15000); } takeRun("custom", -1, obs[sub].time, customRun); } dim.log("Task finished [CUSTOM]."); dim.log(""); break; // case "CUSTOM" case "DATA": // ========================== case "DATA" ============================ /* 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==undefined ? 0 : (nextObs.start-new Date())/60000; //dim.log("DEBUG: remaining: "+remaining+" nextObs="+nextObs+" start="+nextObs.start); // ------------------------------------------------------------ dim.log("Run count "+run+" [remaining "+parseInt(remaining)+"min]"); // ----- Time since last DRS Calibration [min] ------ var diff = getTimeSinceLastDrsCalib(); // 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 && !obs[sub].orbit) || run==0; // undefined==null -> true! // 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)) || diff==null; if (point) { // Switch the voltage to a reduced voltage level service_feedback.voltageOn(0); // Change wobble position every four runs, // start with alternating wobble positions each day var wobble = (parseInt(run/4) + parseInt(new Date()/1000/3600/24-0.5))%2+1; var angle = obs[sub].angle == null ? Math.random()*360 : obs[sub].angle; if (obs[sub].orbit) // != undefined, != null, != 0 dim.log("Pointing telescope to '"+obs[sub].source+"' [orbit="+obs[sub].orbit+"min, angle="+angle+"]"); else dim.log("Pointing telescope to '"+obs[sub].source+"' [wobble="+wobble+"]"); // This is a workaround to make sure that we really catch // the new OnTrack state later and not the old one dim.send("DRIVE_CONTROL/STOP"); dim.wait("DRIVE_CONTROL", "Initialized", 15000); if (triggerReloadSources) // It's all synchronous - no need to wait here { dim.send("DRIVE_CONTROL/RELOAD_SOURCES"); triggerReloadSources = false; dim.log("Reloading source table triggered in drivectrl."); } if (obs[sub].orbit) // != undefined, != null, != 0 dim.send("DRIVE_CONTROL/TRACK_ORBIT", angle, obs[sub].orbit, obs[sub].source); else dim.send("DRIVE_CONTROL/TRACK_WOBBLE", wobble, obs[sub].source); // Do we have to check if the telescope is really moving? // We can cross-check the SOURCE service later } if (drscal) { doDrsCalibration("data"); // will turn voltage off // Now we switch on the voltage and a significant amount of // time has been passed, so do the check again. sun = Sun.horizon(-12); if (!was_up && sun.isUp) { dim.log("Sun rise detected...."); continue; } } if (irq) continue; OpenLid(); // 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 check the voltage... (do not start a lot of stuff just to do nothing) var state = dim.state("FEEDBACK").name; if (state=="Warning" || state=="Critical" || state=="OnStandby") { v8.sleep(60000); continue; } // Now we are 'OnTrack', so we can ramp to nominal voltage // and wait for the feedback to get stable service_feedback.voltageOn(); service_feedback.waitForVoltageOn(); // If pointing had changed, do calibration if (!irq && point) { dim.log("Starting calibration."); // Calibration (2% of 20') while (!irq) { if (irq || !takeRun("pedestal", 1000)) // 80 Hz -> 10s continue; // if (irq || !takeRun("light-pulser-ext", 1000)) // 80 Hz -> 10s // continue; break; } } //console.out(" Taking data: start [5min]"); // FIXME: What do we do if during calibration something has happened // e.g. drive went to ERROR? Maybe we have to check all states again? var twilight = Sun.horizon(-16).isUp; if (twilight) { for (var i=0; i<5 && !irq; i++) takeRun("data", -1, 60); // Take data (1min) } else { var len = 300; while (!irq && len>15) { var time = new Date(); if (takeRun("data", -1, len)) // Take data (5min) break; len -= parseInt((new Date()-time)/1000); } } //console.out(" Taking data: done"); run++; continue; // case "DATA" } if (nextObs!=undefined && sub==obs.length-1) dim.log("Next observation will start at "+nextObs.start.toUTCString()+" [id="+nextObs.id+"]"); sub++; } sub_drsruns.close(); dim.log("Left main loop [irq="+irq+"]"); // ================================================================ // Comments and ToDo goes here // ================================================================ // error handline : http://www.sitepoint.com/exceptional-exception-handling-in-javascript/ // classes: http://www.phpied.com/3-ways-to-define-a-javascript-class/