/*
 * positioner.h: Steerable dish positioning
 *
 * See the main source file 'vdr.c' for copyright information and
 * how to reach the author.
 *
 * $Id: positioner.h 3.3 2013/12/28 11:15:56 kls Exp $
 */

#ifndef __POSITIONER_H
#define __POSITIONER_H

#include "thread.h"
#include "tools.h"

/// A steerable satellite dish generally points to the south on the northern hemisphere,
/// and to the north on the southern hemisphere (unless you're located directly on the
/// equator, in which case the general direction is "up"). Therefore, moving the dish
/// "east" or "west" means something different on either hemisphere. From the local dish
/// motor's point of view, it makes more sense to speak of turning the dish "left" or
/// "right", which is independent of the actual hemisphere the dish is located in.
/// In the cPositioner class context, when a dish on the northern hemisphere moves "east",
/// it is considered to be moving "left". Imagine standing behind the dish and looking
/// towards the satellites, and clearly "east" is "left". On the southern hemisphere
/// the same move to the "left" would go to the "west". So on the hardware level it is
/// clear what "left" and "right" means. The user interface may present different labels
/// to the viewer, depending on the hemisphere the dish is on.
/// All angles in this context are given in "degrees * 10", which allows for an angular
/// resolution of 0.1 degrees.

class cPositioner {
private:
  mutable cMutex mutex;
  static cPositioner *positioner;
  int capabilities;
  int frontend; // file descriptor of the DVB frontend
  mutable int lastLongitude; // the longitude the dish has last been moved to
  int targetLongitude; // the longitude the dish is supposed to be moved to
  mutable int lastHourAngle; // the hour angle the positioner has last been moved to
  int targetHourAngle; // the hour angle the positioner is supposed to be moved to
  int swingTime;
  cTimeMs movementStart;
protected:
  cPositioner(void);
  virtual ~cPositioner();
  void SetCapabilities(int Capabilities) { capabilities = Capabilities; }
      ///< A derived class shall call this function in its constructor to set the
      ///< capability flags it supports.
  int Frontend(void) const { return frontend; }
      ///< Returns the file descriptor of the DVB frontend the positioner is
      ///< connected to. If the positioner is not connected to any DVB device,
      ///< -1 will be returned.
  static int CalcHourAngle(int Longitude);
      ///< Takes the longitude and latitude of the dish location from the system
      ///< setup and the given Longitude to calculate the "hour angle" to which to move
      ///< the dish to in order to point to the satellite at orbital position Longitude.
      ///< An hour angle of zero means the dish shall point directly towards the
      ///< celestial equator (which is south on the northern hemisphere, and north on
      ///< the southern hemisphere). Negative values mean that the dish needs to be
      ///< moved to the left (as seen from behind the dish), while positive values
      ///< require a movement to the right.
  static int CalcLongitude(int HourAngle);
      ///< Returns the longitude of the satellite position the dish points at when the
      ///< positioner is moved to the given HourAngle.
  void StartMovementTimer(int Longitude);
      ///< Starts a timer that estimates how long it will take to move the dish from
      ///< the current position to the one given by Longitude. The default implementation
      ///< of CurrentLongitude() uses this timer.
public:
  enum ePositionerCapabilities {
    pcCanNothing         = 0x0000,
    pcCanDrive           = 0x0001,
    pcCanStep            = 0x0002,
    pcCanHalt            = 0x0004,
    pcCanSetLimits       = 0x0008,
    pcCanDisableLimits   = 0x0010,
    pcCanEnableLimits    = 0x0020,
    pcCanStorePosition   = 0x0040,
    pcCanRecalcPositions = 0x0080,
    pcCanGotoPosition    = 0x0100,
    pcCanGotoAngle       = 0x0200,
    };
  enum ePositionerDirection { pdLeft, pdRight };
  static int NormalizeAngle(int Angle);
          ///< Normalizes the given Angle into the range -1800...1800.
  int Capabilities(void) const { return capabilities; }
          ///< Returns a flag word defining all the things this positioner is
          ///< capable of.
  void SetFrontend(int Frontend) { frontend = Frontend; }
          ///< This function is called whenever the positioner is connected to
          ///< a DVB frontend.
  static int HorizonLongitude(ePositionerDirection Direction);
          ///< Returns the longitude of the satellite position that is just at the
          ///< horizon when looking in the given Direction. Note that this function
          ///< only delivers reasonable values for site latitudes between +/-81 degrees.
          ///< Beyond these limits (i.e. near the north or south pole) a constant value
          ///< of 0 will be returned.
  int HardLimitLongitude(ePositionerDirection Direction) const;
          ///< Returns the longitude of the positioner's hard limit in the given
          ///< Direction. Note that the value returned here may be larger (or smaller,
          ///< depending on the Direction) than that returned by HorizonLongitude(),
          ///< which would mean that it lies below that horizon.
  int LastLongitude(void) const { return lastLongitude; }
          ///< Returns the longitude the dish has last been moved to.
  int TargetLongitude(void) const { return targetLongitude; }
          ///< Returns the longitude the dish is supposed to be moved to. Once the target
          ///< longitude has been reached, this is the same as the value returned by
          ///< CurrentLongitude().
  virtual cString Error(void) const { return NULL; }
          ///< Returns a short, single line string indicating an error condition (if
          ///< the positioner is able to report any errors).
          ///< NULL means there is no error.
  virtual void Drive(ePositionerDirection Direction) {}
          ///< Continuously move the dish to the given Direction until Halt() is
          ///< called or it hits the soft or hard limit.
  virtual void Step(ePositionerDirection Direction, uint Steps = 1) {}
          ///< Move the dish the given number of Steps in the given Direction.
          ///< The maximum number of steps a particular positioner can do in a single
          ///< call may be limited.
          ///< A "step" is the smallest possible movement the positioner can make, which
          ///< is typically 0.1 degrees.
  virtual void Halt(void) {}
          ///< Stop any ongoing motion of the dish.
  virtual void SetLimit(ePositionerDirection Direction) {}
          ///< Set the soft limit of the dish movement in the given Direction to the
          ///< current position.
  virtual void DisableLimits(void) {}
          ///< Disables the soft limits for the dish movement.
  virtual void EnableLimits(void) {}
          ///< Enables the soft limits for the dish movement.
  virtual void StorePosition(uint Number) {}
          ///< Store the current position as a satellite position with the given Number.
          ///< Number can be in the range 1...255. However, a particular positioner
          ///< may only have a limited number of satellite positions it can store.
  virtual void RecalcPositions(uint Number) {}
          ///< Take the difference between the current actual position of the dish and
          ///< the position stored with the given Number, and apply the difference to
          ///< all stored positions.
  virtual void GotoPosition(uint Number, int Longitude);
          ///< Move the dish to the satellite position stored under the given Number.
          ///< Number must be one of the values previously used with StorePosition().
          ///< The special value 0 shall move the dish to a "reference position",
          ///< which usually is due south (or north, if you're on the southern hemisphere).
          ///< Longitude will be used to calculate how long it takes to move the dish
          ///< from its current position to the given Longitude.
          ///< A derived class must call the base class function to have the target
          ///< longitude stored.
  virtual void GotoAngle(int Longitude);
          ///< Move the dish to the given angular position. Longitude can be in the range
          ///< -1800...+1800. A positive sign indicates a position east of Greenwich,
          ///< while western positions have a negative sign. The absolute value is in
          ///< "degrees * 10", which allows for a resolution of 1/10 of a degree.
          ///< A derived class must call the base class function to have the target
          ///< longitude stored.
  virtual int CurrentLongitude(void) const;
          ///< Returns the longitude the dish currently points to. If the dish is in motion,
          ///< this may be an estimate based on the angular speed of the positioner.
          ///< The default implementation takes the last and target longitude as well as
          ///< the rotation speed of the positioner to calculate the estimated current
          ///< longitude the dish points to.
  virtual bool IsMoving(void) const;
          ///< Returns true if the dish is currently moving as a result of a call to
          ///< GotoPosition() or GotoAngle().
  static cPositioner *GetPositioner(void);
          ///< Returns a previously created positioner. If no plugin has created
          ///< a positioner, there will always be the default DiSEqC positioner.
  static void DestroyPositioner(void);
          ///< Destroys a previously created positioner.
  };

#endif //__POSITIONER_H