/**
 * @fileOverview This file has functions related to documenting JavaScript.
 * @author <a href="mailto:thomas.bretz@epfl.ch">Thomas Bretz</a>
 */
'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 <red>...</red>?
//
// ----------------------------------------------------------------

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 happens, but why?
    if (!evt.obj['status'])
        return;

    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.reset.length; i++)
        dim.send("FAD_CONTROL/DISCONNECT", this.reset[i]);

    v8.sleep(3000);

    while (this.reset.length)
        dim.send("FAD_CONTROL/CONNECT", this.reset.pop());

    v8.sleep(1000);
    dim.wait("FAD_CONTROL", "Connected", 3000);

    console.out("  Reconnect: end");

    return true;
}

function takeRun(type, count, time)
{
    if (!count)
        count = -1;
    if (!time)
        time = -1;

    console.out("  Take run N="+count+" T="+time+"s ["+type+"]");
    // change rats for cal runs1!!!

    dim.send("MCP/START", time?time:-1, count?count:-1, type);

    

    // What could be a reasonable timeout here?
    // FIXME: Replace by callback?
    //
    // DN: I believe instead of waiting for 'TakingData' one could split this
    // up into two checks with an extra condition:
    //  if type == 'data':
    //      wait until ThresholdCalibration starts:
    //          --> 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
    dim.wait("RATE_CONTROL", "Connected", 3000);

    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))
{
    throw new Error"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...");
}

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; i<observations.length; i++)
{
    observations[i] = new Observation(observations[i]);
    
    // check if the start date given by the user is increasing.
    if (i>0 && 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<observations.length; i++)
        if (now<observations[i].start)
            return i-1;

    return observations.length-1;
}


// DN: using so called magic numbers to encode certain 
//      states of the logic is considered pretty bad coding as far as I understand.
//      the reader at this point has no idea why run is -2 ... this is the first time she
//      reads about this variable and there is not a word of explanation found.
var run = -2;
var lastObs;

var test = getObservation();
if (test!=undefined && test<observations.length)
{
    var t = observations[test].start;
    var n = new Date();
    if (t>n)
        console.out(n.toUTCString()+": First observation scheduled for "+t.toUTCString());
    else
        console.out(n.toUTCString()+": First observation should start immediately.");
    if (t>n+12*3600*1000)
        console.out(n.toUTCString()+": No observations scheduled for the next 12 hours!");
}

while (1)
{
    // Check if observation position is still valid
    // If source position has changed, set run=0
    var idxObs = getObservation();
    if (idxObs===undefined)
        exit();

    if (idxObs==-1)
    {
        v8.sleep(1000);
        continue;
    }

    var obs     = observations[idxObs];
    var nextObs = observations[idxObs+1];

    // Check if observation target has changed
    if (lastObs!=idxObs)
    {
        console.out("--- "+idxObs+" ---");
        console.out("Current time:        "+new Date());
        console.out("Current observation: "+obs);
        console.out("Next    observation: "+nextObs);
        console.out("");

        // 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
        if (run==-2)
        {
            Startup();   // -> 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", 5000);

        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?

        // [TB] Either here the system have to be brought to the correct state, or
        //      it has to be ensured that the previous code brought the system to a correct
        //      state. In this case checkStates could be used (but is a bit of obsolete)
        //      Doing non-throwing checks without checking the return value does not make
        //      sense at all.
        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;

    // ------------------------------------------------------------

    // Checking for 'Ramping' in the BIAS_CONTROL is not ideal, but at the moment
    // it is not possible to distinguish between a real ramping and the short
    // ramping which takes place whenever the feedback updated the voltages.

    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", "Ramping"       ] ],
         [ "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))
    {
        //dim.alarm("System inconsistent");
        //dim.alarm();
        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: %.1fmin ago".$(diff));
    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)
    {
        // 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;

        //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)
            break;

        len -= parseInt((new Date()-time)/1000);
    }

    //v8.sleep(360000);
    console.out("  Taking data: done");

    run++;
}

service_drs.close();
