/// \file Pneumatic.h
/// \brief Pneumatic channel controller for one valve set.
/// \copyright Copyright (c) 2015-2016, Garth Zeglin.  All rights reserved. Licensed under the terms of the BSD 3-clause license.
/// \details Pneumatic axis controller for one valve set.  Different modes are available, including using separate fill and empty valves.

#ifndef __PNEUMATIC_H_INCLUDED__
#define __PNEUMATIC_H_INCLUDED__

#include <Arduino.h>
#include <stdint.h>

/// An instance of this class manages generation of fill and empty signals for
/// one pneumatic channel.
class Pneumatic {

public:
  /// Valve configuration for the channel.
  enum pneumatic_config_t { FILL_EMPTY,            ///< pair of two-way valves: one fill, one empty
			    PROP_FILL_PROP_EMPTY,  ///< pair of proportional two-way valves: one fill one empty
			    FILL_THREEWAY          ///< single three-way valve: fills when active, empties otherwise
  };

private:
  /****************************************************************/
  // The following instance variables may only be modified from a non-interrupt
  // context, i.e., not within poll().

  /// The I/O pins for this channel designated using the Arduino convention.
  int8_t fill_pin, empty_pin;

  /// Configuration of the valve set for this axis.
  enum pneumatic_config_t config;

  /// Current actuator state.
  bool fill, empty;

  /// Countdown for the current actuator signal phase, in microseconds.
  long timer;

  /// Duration of the full cycle, in microseconds.
  long period;

  /// Duration of the active period.  Positive values fill, negative values empty.
  long active;

  /// Current duty cycle fraction (-1 to 1).
  float duty;

  /// Current control mode for single axis.
  enum pneumatic_state_t { IDLE,       ///< no air flow, commands ignored
			   OPEN_LOOP,  ///< open-loop control with valve speed control via PWM
			   NUM_PNEUMATIC_MODES   ///< the total number of axis modes
  } mode;

  /// Utility function to set valve state and flags.
  void set_valves( bool _fill, bool _empty) {
    fill = _fill;
    empty = _empty;

    switch (config) {
    case FILL_EMPTY:
      digitalWrite( fill_pin,  (fill) ? (HIGH) : (LOW));
      digitalWrite( empty_pin, (empty) ? (HIGH) : (LOW));
      break;

    case PROP_FILL_PROP_EMPTY:
      // bypass the soft PWM if the valves are proportional
      if (duty > 0.0) analogWrite(fill_pin, (int) (duty * 255));
      else analogWrite(fill_pin, 0);

      if (duty < 0.0) analogWrite(empty_pin, (int) (-duty * 255));
      else analogWrite(empty_pin, 0);
      break;

    case FILL_THREEWAY:
      digitalWrite( fill_pin,  (fill) ? (HIGH) : (LOW));
      break;
    }
  }
  /****************************************************************/

public:

  /// Default constructor called during array initialization.  Note that this is
  /// a no-op, since each object needs to be properly initalized with unique pin
  /// numbers later.
  Pneumatic() { }

  /// Main constructor.  The arguments are the pin numbers for the step and
  /// direction outputs. Note: this does not initialize the underlying hardware.
  /// Unused outputs should supply a pin number of -1.
  Pneumatic( int8_t _fill_pin, int8_t _empty_pin, enum pneumatic_config_t _config) {
    fill_pin  = _fill_pin;
    empty_pin = _empty_pin;
    config    = _config;
    fill      = false;
    empty     = false;
    timer     = 0;
    period    = 50000;
    active    = 0;
    duty      = 0.0;
    mode      = IDLE;
  }

  /// Hardware initialization.  This class follows the Arduino convention of
  /// initializing instance data in the constructor but not hardware.
  void begin(void) {
    if (fill_pin >= 0){
      pinMode( fill_pin, OUTPUT );
      digitalWrite( fill_pin, LOW );
    }
    if (empty_pin >= 0) {
      pinMode( empty_pin, OUTPUT );
      digitalWrite( empty_pin, LOW );
    }
  }

  /// Set the valve duty cycle and enable open-loop output mode.
  /// The duty cycle is specified from -1.0 to 1.0; positive values fill, negative values empty.
  void setDutyCycle(float _duty) {
    duty   = constrain(_duty, -1.0, 1.0);
    mode   = OPEN_LOOP;
    active = duty * period;
  }

  /// Main polling function to be called as often as possible.  The interval
  /// argument is the duration in microseconds since the last call.
  void update(long interval);

  /// Send a status output message to the serial port.  This should not be called too often to avoid stalling.
  void send_status(void);

};

#endif //__PNEUMATIC_H_INCLUDED__
