/**
* @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();