/* 
 * SimulatorFans
 *   Copyright (c) 2017 Mark van Renswoude
 *   https://git.x2software.net/pub/SimulatorFans
 *   
 *   
 * Accepts serial commands to control one or more fans.
 *  
 * All commands are terminated with a #10 character and are case sensitive.
 * Values are passed as ASCII text. This makes it easy to test it using the 
 * Arduino's Serial Monitor.
 * 
 * Baud rate is 19200 by default.
 * 
 * 
 * Commands:
 *   >Info
 *     Used for validating that the device is actually a SimulatorFans device.
 *     Returns the number of connected fans.
 *     
 *     Example response:
 *       <Info:Fans=2
 *     
 *   >GetFans
 *     Returns the currently set values for all fans.
 *     
 *     Example response:
 *       <GetFans:0,255
 *   
 *   >SetFans:v1,v2,v...
 *     Updates the fan values. Each value ranges from 0 to 255.
 *     If the first value is 'A', the second value is applied to each fan.
 *     
 *     When in Servo mode, if the first value is 'M', the Servo is
 *     set to it's maximum pulse width, disregarding the MaxValue.
 *     Used for ESC calibration. USE WITH CARE!
 *     
 *     Example response:
 *       <SetFans
 */

     
/* 
 * Configuration
 */

// Defines the method used for controlling the fans.
#define ModePWM 0
#define ModeServo 1

// ModePWM:
//   Fans are connected through a transistor (or preferably MOSFET) directly
//   from the pins. The PWM frequency is set low to reduce noise.
//
// ModeServo:
//   Intended for use with an ESC (Electronic Speed Controller). The PWM signal
//   on each pin will be compatible with standard ESCs or servos.
#define FanMode ModeServo


// The number of fans connected. Each fan must connect to it's own
// PWM-enabled or Servo-supported pin.
#define FanCount 1

// The pin on which each of the fans is connected. The number of
// values must be equal to FanCount (not sure why the compiler
// doesn't enforce this if FanCount is bigger). The order is
// assumed to be left to right.
const byte FanPin[FanCount] =
{
  9
};


// When a fan goes from completely off to a value below full, this
// determines how long the fan will run on a higher value before changing
// to the actual value, to give it a chance to start up.
#define StartingFansTime 200

// The value used when the fan is starting up (range 0 - 255)
#define StartingValue 128


// The minimum value at which the fan still spins reliably. Anything below
// will be considered as 0.
#define MinimumValue 30


/*
 * Servo mode configuration
 */
// The minimum pulse width. Servo library uses a default of 544.
#define ServoMinPulseWidth 1000
 
// The maximum pulse width. Servo library uses a default of 2400.
#define ServoMaxPulseWidth 2000

#define ServoPulseRange (ServoMaxPulseWidth - ServoMinPulseWidth)


// The pulse width when the input value is 0, in microseconds.
// Using microseconds instead of the Servo library's simpler 0-180 degree mapping
// allows for more finegrained changes when limiting the values.
#define ServoMinValue ServoMinPulseWidth

// The pulse width when the input value is 255, in microseconds.
// Use this to limit the maximum "throttle" to the ESC.
#define ServoMaxValue ServoMinPulseWidth + ServoPulseRange


/*
 * Actual code. Lasciate ogne speranza, voi ch'intrate.
 */
#define IsPWM (FanMode == ModePWM)
#define IsServo (FanMode == ModeServo)


#if IsServo
#include <Servo.h>
#endif
 
typedef struct
{
  byte value;
  unsigned long startTime;

  #if IsServo
  Servo servo;
  #endif
} FanStatus;


unsigned long currentTime = 0;
FanStatus fanStatus[FanCount];


// Forward declarations
void handleInfoCommand();
void handleGetFansCommand();
void handleSetFansCommand();
void handleUnknownCommand(char* command);

void checkStartingFans();
void setFan(byte fan, byte value);


void setup() 
{
  memset(fanStatus, 0, sizeof(fanStatus));

  for (byte fan = 0; fan < FanCount; fan++)
  {
    #if IsPWM
    pinMode(FanPin[fan], OUTPUT);
    #endif

    #if IsServo
    fanStatus[fan].servo.attach(FanPin[fan]);
    fanStatus[fan].servo.writeMicroseconds(ServoMinValue);
    #endif
  }
    

  #if IsPWM
  // Set all clock select bits to clk/1024. This will result in a 30 ~ 60 Hz PWM frequency. 
  // The default frequency causes annoying noises in the fan, the low frequency is still
  // audible but good enough since the game sounds should easily drown that out.
  // It messes up the millis() function though, so compensate for that later on.
  TCCR0B = TCCR0B & 0b11111000 | 0b101;
  TCCR1B = TCCR1B & 0b11111000 | 0b101;
  TCCR2B = TCCR2B & 0b11111000 | 0b101;

  // 64 is the default prescaler
  #define correctedMillis() (millis() * (1024 / 64))
  #endif


  #if IsServo
  #define correctedMillis() millis()
  #endif
  

  // Set up serial communication (through USB or the default pins)
  // 19.2k is fast enough for our purpose, and according to the ATMega's datasheet 
  // has a low error percentage across the common oscillator frequencies.
  Serial.begin(19200);
}


char command[50];
byte commandLength;
byte offset;
char* token;


void loop() 
{
  currentTime = correctedMillis();
  checkStartingFans();

  if (Serial.available() > 0)
  {
    // Try to read a serial command
    memset(command, 0, sizeof(command));
    commandLength = Serial.readBytesUntil('\n', command, sizeof(command) - 1);
    if (commandLength > 0 && commandLength < sizeof(command))
    {
      // The Windows application always sends two 0xFF characters when it first connects.
      // I'm sure it's a setting somewhere, but I couldn't figure it out just yet. Instead,
      // simply look for the start of the command, thus skipping all other bytes. A bit
      // of a hack, but if you know the proper solution, I'd love to hear it!
      offset = 0;

      while (offset < commandLength && command[offset] != '>')
        offset++;
      
      token = strtok(&command[offset], ":");
      if (token != NULL)
      {
        if (strcmp(token, ">Info") == 0)
          handleInfoCommand();
        else if (strcmp(token, ">GetFans") == 0)
          handleGetFansCommand();
        else if (strcmp(token, ">SetFans") == 0)
          handleSetFansCommand();
        else
          handleUnknownCommand(token);
      }
    }
  }
}


void handleInfoCommand()
{
  Serial.write("<Info:Fans=");
  Serial.print(FanCount);
  Serial.write(",Mode=");
  #if IsPWM
  Serial.write("PWM");
  #endif
  #if IsServo
  Serial.write("Servo");
  #endif
  Serial.write("\n");
}


void handleGetFansCommand()
{
  Serial.write("<GetFans:");

  for (byte fan = 0; fan < FanCount; fan++)
  {
    if (fan > 0) 
      Serial.write(",");
      
    Serial.print(fanStatus[fan].value);
  }
  
  Serial.write("\n");
}


void handleSetFansCommand()
{
  token = strtok(NULL, ",");
  if (token != NULL)
  {
    if (strcmp(token, "A") == 0)
    {
      // Set all fans to the same value
      token = strtok(NULL, ",");
      if (token != NULL)
      {
        int value = atoi(token);
        
        if (value < 0) value = 0;
        if (value > 255) value = 0;
        
        for (byte fan = 0; fan < FanCount; fan++)
          setFan(fan, value);
      }
    }
    #if IsServo
    else if (strcmp(token, "M") == 0)
    {
      for (byte fan = 0; fan < FanCount; fan++)
      {
        // Join me for a drive, maximum overdrive!
        fanStatus[fan].servo.writeMicroseconds(ServoMaxPulseWidth);
        fanStatus[fan].value = 255;
      }
    }
    #endif
    else
    {
      // Set individual fan values
      for (byte fan = 0; fan < FanCount; fan++)
      {
        int value = atoi(token);
        
        if (value < 0) value = 0;
        if (value > 255) value = 0;
        
        setFan(fan, value);

        token = strtok(NULL, ",");
        if (token == NULL)
          break;
      }
    }
  }
  
  Serial.write("<SetFans\n");
}


void handleUnknownCommand(char* command)
{
  Serial.write("<Error:unknown command:");
  Serial.write(command);
  Serial.write("\n");
}


void checkStartingFans()
{
  // Check if any of the fans are currently starting up
  for (byte fan = 0; fan < FanCount; fan++)
  {
    if ((fanStatus[fan].startTime > 0) && 
        (currentTime - fanStatus[fan].startTime >= StartingFansTime))
    {
      fanStatus[fan].startTime = 0;
      setFan(fan, fanStatus[fan].value);
    }
  }
}

#if IsPWM
#define writeFanValue(fan, value) analogWrite(FanPin[fan], value)
#endif
#if IsServo
#define writeFanValue(fan, value) fanStatus[fan].servo.writeMicroseconds(map(value, 0, 255, ServoMinValue, ServoMaxValue))
#endif

void setFan(byte fan, byte value)
{
  byte correctedValue = value;
  if (correctedValue < MinimumValue)
    correctedValue = 0;
  
  if ((fanStatus[fan].value == 0 || fanStatus[fan].startTime > 0) && correctedValue > 0)
  {
    // Fan was off or still starting up, start with full power to kick it off   
    writeFanValue(fan, StartingValue);
    
    if (fanStatus[fan].startTime == 0)
    {
      fanStatus[fan].startTime = currentTime;
    }
  }
  else
  {
    // Already running, simply change the speed and reset the start time if necessary
    fanStatus[fan].startTime = 0;
    writeFanValue(fan, correctedValue);
  }
  
  fanStatus[fan].value = correctedValue;
}
