/*******************************************************************************************
  SIMPL+ Module Information
  (Fill in comments below)
*******************************************************************************************/
/*
Dealer Name:  
System Name:  Anthem STR
System Number:
Programmer:   Richard Mullins
Comments:
*/

/*******************************************************************************************
  Compiler Directives
  (Uncomment and declare compiler directives as needed)
*******************************************************************************************/
// #ENABLE_DYNAMIC
#SYMBOL_NAME "Anthem STR"
// #HINT ""
// #DEFINE_CONSTANT
// #CATEGORY "" 
#CATEGORY "28" // Receiver/Processor
// #PRINT_TO_TRACE
// #DIGITAL_EXPAND 
// #ANALOG_SERIAL_EXPAND 
// #OUTPUT_SHIFT 
// #HELP_PDF_FILE ""
#DEFAULT_VOLATILE
#ENABLE_STACK_CHECKING
#ENABLE_TRACE
// #ENCODING_ASCII
// #ENCODING_UTF16
// #ENCODING_INHERIT_FROM_PARENT
// #ENCODING_INHERIT_FROM_PROGRAM

#HELP_BEGIN
This module allows for control over the Anthem STR preamplifiers and
amplifiers. The module can be used with either ip or rs232 connections.

-- SIGNALS --
Type Name            Description
 D   enable          module wont send commands until this is high
 D   Refresh         The will query all the current values for the amp
 D   PowerON/OFF     This will turn the STR on or off
 D   PowerToggle     This will toggle the stage (based on the current state)
 D   Volume Up/Down  This will raise or lower the volume by the value set in the volume Skip parameter

#HELP_END


/*******************************************************************************************
  Include Libraries
  (Uncomment and include additional libraries as needed)
*******************************************************************************************/
// #CRESTRON_LIBRARY ""
// #USER_LIBRARY ""

/*******************************************************************************************
  DIGITAL, ANALOG and SERIAL INPUTS and OUTPUTS
  (Uncomment and declare inputs and outputs as needed)
*******************************************************************************************/
DIGITAL_INPUT _SKIP_;
DIGITAL_INPUT enable, Refresh;
DIGITAL_INPUT _SKIP_;
DIGITAL_INPUT PowerON, PowerOFF, PowerToggle;
DIGITAL_INPUT _SKIP_;
DIGITAL_INPUT VolumeUp, VolumeDown, Mute;
DIGITAL_INPUT _SKIP_;
DIGITAL_INPUT Trigger, GetSampleRate, GetInputFormat;
DIGITAL_INPUT _SKIP_;
ANALOG_INPUT Volume, InputSelect, Balance;
ANALOG_INPUT _SKIP_;
ANALOG_INPUT IRCommand, CurrentInputProfile;
STRING_INPUT _SKIP_;
STRING_INPUT VolumeIn$[6];
STRING_INPUT _SKIP_,_SKIP_,_SKIP_,_SKIP_,_SKIP_, _SKIP_,_SKIP_,_SKIP_;
STRING_INPUT Rx$[255];
//STRING_INPUT Inject$[255];
STRING_INPUT _SKIP_;
ANALOG_INPUT InputProfile[30,30];
// BUFFER_INPUT 

DIGITAL_OUTPUT _SKIP_;
DIGITAL_OUTPUT enabled;
DIGITAL_OUTPUT _SKIP_, _SKIP_;
DIGITAL_OUTPUT PowerFB;
DIGITAL_OUTPUT _SKIP_,_SKIP_,_SKIP_,_SKIP_,_SKIP_;
DIGITAL_OUTPUT MuteFB;
DIGITAL_OUTPUT _SKIP_;
ANALOG_OUTPUT TriggerType, SampleRate, InputFormatFB;
ANALOG_OUTPUT TotalInputs;
ANALOG_OUTPUT VolumeFB, InputFB, BalanceFB;
ANALOG_OUTPUT _SKIP_;
ANALOG_OUTPUT ListenModeFB, FrontPanel;
ANALOG_OUTPUT _SKIP_;
STRING_OUTPUT Volume_FB$,Current_Source$;
STRING_OUTPUT _SKIP_;
STRING_OUTPUT Model, Firmware, FirmwareDate, Hardware, MAC;
STRING_OUTPUT _SKIP_;
STRING_OUTPUT Tx$;
STRING_OUTPUT _SKIP_;
ANALOG_OUTPUT InputProfileFB[30,30];
ANALOG_OUTPUT _SKIP_;
STRING_OUTPUT InputName[30,14];




/*******************************************************************************************
  SOCKETS
  (Uncomment and define socket definitions as needed)
*******************************************************************************************/
// TCP_CLIENT AnthemSTR[1024];
// TCP_SERVER
// UDP_SOCKET

/*******************************************************************************************
  Parameters
  (Uncomment and declare parameters as needed)
*******************************************************************************************/
INTEGER_PARAMETER volumeSkip; //, port;
// SIGNED_INTEGER_PARAMETER
// LONG_INTEGER_PARAMETER
// SIGNED_LONG_INTEGER_PARAMETER
// STRING_PARAMETER ipAddress[16];

/*******************************************************************************************
  Parameter Properties
  (Uncomment and declare parameter properties as needed)
*******************************************************************************************/
#BEGIN_PARAMETER_PROPERTIES volumeSkip
   propValidUnits = unitDecimal;
   propDefaultUnit = unitDecimal;
   propBounds = 1d , 100d;
   propDefaultValue = 10d;  
#END_PARAMETER_PROPERTIES
       
/*
#BEGIN_PARAMETER_PROPERTIES port
   propValidUnits = unitDecimal;
   propDefaultUnit = unitDecimal;
   propBounds = 1024d , 65535d;
   propDefaultValue = 14999d;  
#END_PARAMETER_PROPERTIES

#BEGIN_PARAMETER_PROPERTIES ipAddress
   propValidUnits = unitString;
   propDefaultUnit = unitString;
   propDefaultValue = "0.0.0.0";  
#END_PARAMETER_PROPERTIES
*/
/*******************************************************************************************
  Structure Definitions
  (Uncomment and define structure definitions as needed)
  Note:  Be sure to initialize all declared STRING variables as needed
         For example, in Function Main: struct.myString = "";
*******************************************************************************************/
/*
STRUCTURE MyStruct1
{
};

MyStruct1 struct;
*/

/*******************************************************************************************
  Global Variables
  (Uncomment and declare global variables as needed)
  Note:  Be sure to initialize all declared STRING variables as needed
         For example, in Function Main: myString = "";
*******************************************************************************************/
// INTEGER
// LONG_INTEGER
// SIGNED_INTEGER
// SIGNED_LONG_INTEGER
STRING g_InputName[30][32];

/*******************************************************************************************
  Functions
  (Add any additional functions here)
  Note:  Functions must be physically placed before the location in
         the code that calls them.
*******************************************************************************************/
integer_function SendCommand(string cmd$) {
  //signed_integer status;
  //string Tx$[32];
  
  makestring(Tx$, "%s;", cmd$);
  //trace("sending: %s", Tx$);
  //status = SocketSend(AnthemSTR, Tx$);

/*
  delay(10);
  
  if (status < 0) {
    trace("Error Sending to Anthem STR: %d\n", status);
    return(1);
  } else {
    return(0);
  }
*/
}

function getPowerStatus() {
  sendCommand("Z1POW?");
}

function getStatus() {
  trace("get status called");
    
  if(PowerFB = 0) return;
 
  sendCommand("IDM?");
  sendCommand("IDS?");
  sendCommand("IDB?");
  sendCommand("IDH?");
  sendCommand("IDN?");
  sendCommand("FPB?");
  sendCommand("ICN?");
  sendCommand("R0CTL?");
  sendCommand("Z1INP?");
  sendCommand("Z1VOL?");
  sendCommand("Z1MUT?");
  sendCommand("Z1BAL?");
  sendCommand("Z1LEV?");
  sendCommand("Z1TON?");
  sendCommand("Z1AIF?");
  sendCommand("Z1SRT?");  
  sendCommand("Z1ALM?");
}

 
function getInputNames(integer total) {
  integer i;
  string cmd$[16];

  trace("getting input names for %u inputs", total);
  for(i = 1 to total) {
    trace("getting input names for input %u", i);
    makestring(cmd$, "ISN%02u?", i);
    sendCommand(cmd$);
    delay(5);
  }
}
 
function getSpeakerProfiles(integer total) {
  integer i;
  string cmd$[16];

  trace("getting profile for %u inputs", total);
  for(i = 1 to total) {
    trace("getting input names for input %u", i);
    makestring(cmd$, "SSP%02u?", i);
    sendCommand(cmd$);
  }
  
}

function processInternal(string command$) {
  integer zone;
  string junk$[64], zoneName$[16];


  if(left(command$, 3) = "IDM") {
    junk$ = remove("IDMSTR ", command$);
    makestring(Model, "%s", command$); 
  }
  if(left(command$, 3) = "IDS") {
    junk$ = remove("IDS", command$);
    makestring(Firmware, "%s", command$);
  }
  if(left(command$, 3) = "IDB") {
    junk$ = remove("IDB", command$);
    makestring(FirmwareDate, "%s", command$);
  }
  if(left(command$, 3) = "IDH") {
    junk$ = remove("IDH", command$);
    makestring(Hardware, "%s", command$);
  }
  if(left(command$, 3) = "IDN") {
    junk$ = remove("IDN", command$);
    makestring(MAC, "%s", command$);

  }
  if(left(command$, 3) = "ICN") {
    TotalInputs = atoi(command$);
    getInputNames(TotalInputs);
    getSpeakerProfiles(TotalInputs);
  }
  if(left(command$, 3) = "ISN") {
    junk$ = remove("ISN", command$);
    zone = atoi(left(command$, 2));
    zoneName$ = right(command$, len(command$) - 2);
    trace("input name for zone %u is %s (%s)", zone, zoneName$, command$);
    g_InputName[zone] = zoneName$;
    makestring(InputName[zone], "%s", zoneName$);
  }
}
 
function processFront(string command$) {
  trace("processing Front Panel command: %s", command$);
  FrontPanel = atoi(command$);
}

function processRelay(string command$) {
  trace("processing Relay command: %s", command$);
  TriggerType = atoi(right(command$, 2));
} 

function processSpeaker(string command$) {
  integer number, profile;
  string junk$[64], name$[32];

  if(left(command$, 3) = "SPN") {
    junk$ = remove("SPN", command$);
    number = atoi(left(command$, 1));
    name$ = right(command$, len(command$) - 2);
    trace("speaker profile %u's name is %s", number, name$);
  }
  if(left(command$, 3) = "SSP") {
    junk$ = remove("SSP", command$);
    number = atoi(left(command$, 2));
    profile = atoi(right(command$, 2));
    trace("input %u is using profile %u", number, profile);
    inputProfileFB[number] = profile;
  }
} 

function processZone(string command$) {
  integer zone, fraction, units;
  string junk$[32];

  trace("processing zone command: %s", command$);
  zone = atoi(left(command$, 2));
  junk$ = remove(left(command$, 2), command$);
  
  if(left(command$, 3) = "POW") {
    if(atoi(command$) != PowerFB) {
      trace("zone %u power has changed to %u", zone, atoi(command$));
      PowerFB = atoi(command$);
      getStatus();
    }
  } 
  if(left(command$, 3) = "INP") {
    trace("zone %u input has changed to %u", zone, atoi(command$));
    Current_Source$ = g_InputName[atoi(command$)];
    InputFB = atoi(command$);
  }
  if(left(command$, 3) = "VOL") {
    junk$ = remove("VOL", command$);
    trace("zone %u volume has changed to %s", zone, command$);
    Volume_FB$ = command$;
    fraction = atoi(right(command$, 1));
    if(left(command$,1) = "-") {
      units = (96 - atoi(command$)) * 2;
    } else {
      units = (96 + atoi(command$)) * 2;
    }

    if(fraction != 0)
      units = units + 1;
  
    VolumeFB = units * 100 / 207 * 655;
  }
  if(left(command$, 3) = "MUT") {
    trace("zone %u mute has changed to %u", zone, atoi(command$));
    MuteFB = atoi(command$);
  }
  if(left(command$, 3) = "BAL") {
    trace("zone %u balance has changed to %u", zone, atoi(command$));
    BalanceFB = atoi(command$) * 655;
  }
  if(left(command$, 3) = "LEV") {
    trace("zone %u level has changed to %u", zone, atoi(command$));
    //TBD
  }
  if(left(command$, 3) = "TON") {
    trace("zone %u tone has changed to %u", zone, atoi(command$));
    //TBD
  }
  if(left(command$, 3) = "AIF") {
    trace("zone %u Input Format has changed to %u", zone, atoi(command$));
    InputFormatFB = atoi(command$);
  }

  if(left(command$, 3) = "SRT") {
    trace("zone %u Sample Rate is %u", zone, atoi(command$));
    SampleRate = atoi(command$);
  }
  if(left(command$, 3) = "ALM") {
    trace("zone %u listening mode has changed to %u", zone, atoi(command$));
    ListenModeFB = atoi(command$);
  }

}

/*******************************************************************************************
  Event Handlers
  (Uncomment and declare additional event handlers as needed)
*******************************************************************************************/
//
//init and module commands
//


PUSH enable {
/*
	SIGNED_INTEGER status;
  
  status = SocketConnectClient(AnthemSTR, ipAddress, port, 1);
  trace("enabling the connection to the AnthemSTR (%d)", status);
      
  if(status < 0) {
    enabled = 0;
    trace("Error listening to %s on port  %u", ipAddress, port);
  }
  else {
    enabled = 1;
    getStatus();
  }
*/
  enabled = 1;
  getStatus();
}

RELEASE enable {
/*
  SIGNED_INTEGER status;

  status = SocketDisconnectClient(AnthemSTR);
  trace("disabling the connection to the AnthemSTR (%d)", status);
                
  enabled = 0;
  if(status < 0)
   trace("Error discinnecting from port %u", port);
*/
  enabled = 0;
}

PUSH refresh {
  if(enabled)
    getPowerStatus();
}

//
// Power control
//
PUSH PowerON {
  sendCommand("Z1POW1");
  sendCommand("Z1POW?");
}

PUSH PowerOFF {
  sendCommand("Z1POW0");
  sendCommand("Z1POW?");
} 

PUSH PowerToggle {
  if(PowerFB)
    sendCommand("Z1POW0");
  else
    sendCommand("Z1POW1");
} 
 
//
// Trigger control
//

PUSH Trigger {
  string cmd$[32];

  if(TriggerType != 1) {
    trace("trigger control is set to menu. Changing to RS232/IP control.");
    sendCommand("R0CTL1");
    TriggerType = 1;
  }
  sendCommand("R0SET1");
}

RELEASE Trigger {
  if(TriggerType != 1) {
    trace("trigger control is set to menu. Changing to RS232/IP control.");
    sendCommand("R0CTL1");
    TriggerType = 1;
  }
  sendCommand("R0SET0");
}

//
// Volume
//
PUSH VolumeUp {
  string cmd$[32];

  makestring(cmd$, "Z1VUP%02u.%01u", volumeSkip / 10, volumeSkip % 10);
  SendCommand(cmd$);
}

PUSH VolumeDown {
  string cmd$[32];

  makestring(cmd$, "Z1VDN%02u.%01u", volumeSkip / 10, volumeSkip % 10);
  SendCommand(cmd$);
}

PUSH Mute {
  sendCommand("Z1MUT1");  
}
 
RELEASE Mute {
  sendCommand("Z1MUT0");
}

CHANGE Volume {
  integer result, fraction;
  signed_integer vol, units;
  string cmd$[32];

  //vol = Volume / 655;
  result = Volume * 206 / 65535;
  trace("result: %u", result);

  units = result / 2 - 96;
  if(result % 2) fraction = 5;

  trace("units: %d, fraction: %u", units, fraction);

  makestring(cmd$, "Z1VOL%d.%u", units, fraction);
  trace("COMMAND: %s", cmd$);
  sendCommand(cmd$);
}
 
CHANGE VolumeIn$ {
  string cmd$[32];

  makestring(cmd$, "Z1VOL%s", VolumeIn$);
  sendCommand(cmd$);
}
   
//
// Input 
//

CHANGE InputSelect {
  string cmd$[32];

  if(InputSelect < 0 || InputSelect > TotalInputs) {
    trace("Invalid input, valid inputs range between 1 and %u", TotalInputs);
    return;
  }
  
  makestring(cmd$, "Z1INP%02u", InputSelect);
  sendCommand(cmd$); 
}

//
// Balance
//
CHANGE Balance {
  string cmd$[32];

  makestring(cmd$, "Z1BAL%03u", Balance / 655);
  sendCommand(cmd$);
}  

//
// IR Command
//
CHANGE IRCommand {
  string cmd$[32];

  if(IRCommand < 10 || IRCommand > 27) {
    trace("Invalid code. Codes must be between 10 and 27");
     return;
  }  

  makestring(cmd$, "Z1SIM%04u", IRCommand);
  sendCommand(cmd$);
}

//
// Speaker Profiles
//
CHANGE CurrentInputProfile {
  string cmd$[32];
  
  if(CurrentInputProfile < 0 && CurrentInputProfile > 5) {
    trace("Invalid profile, valid inputs range between 1 and 4");
    return;   
  }
  makestring(cmd$, "SSP00%u", CurrentInputProfile);
  sendCommand(cmd$);
}

CHANGE InputProfile {
  integer inputNumber, newProfile, oldProfile;
  string cmd$[32];     
                 
  inputNumber = GetLastModifiedArrayIndex();
  newProfile = inputProfile[inputNumber];
  oldProfile = inputProfileFB[inputNumber];

  if(newProfile = oldProfile)
    return;

  if(newProfile > 0 && newProfile < 5) {
    trace("profile for input %u has changed from %u to %u, updating", inputNumber, oldProfile, newProfile);
    makestring(cmd$, "SSP%02u%u", inputNumber, newProfile);
    inputProfileFB[inputNumber] = newProfile;
    sendCommand(cmd$);
  }
}

//
// Get functions
//
PUSH GetSampleRate {
  sendCommand("Z1SRT?");
}

PUSH GetInputFormat {
  sendCommand("Z1AIF?");
}

//
// Data processing
//          

CHANGE Rx$ {
  integer firstChar, i;
  string In$[1024], command$[512], cmd$[16], param$[128];

  In$ = gather(";", Rx$);
  ClearBuffer(Rx$);

  command$ = left(In$, find(";", In$) - 1);
  firstChar = byte(command$, 1);

  trace("RECV: %s", command$);
                                
  switch(firstChar) {
    case (70):  //command starts with F (front panel)
      processFront(command$);
    case (73):  //command starts with I (internal)
      processInternal(command$);
    case (82):  //command starts with R (relay)
      processRelay(command$);
    case (83):  //command starts with S (speaker)
      processSpeaker(command$);
    case (90):  //command starts with Z (zone)
      processZone(command$);
    default:
      trace("unknown command: %s", command$);
  }
}

//
// TESTING
//  

/*
CHANGE inject$ {
  sendCommand(inject$);
}
/*    

//
// Socket code
//
   
/*
SOCKETCONNECT AnthemSTR {
  trace("Connection to the Anthem established");
}


SOCKETDISCONNECT AnthemSTR {
  trace("Connection to the Anthem terminated");
}

SOCKETRECEIVE AnthemSTR {
  integer firstChar, i;
  string Rx$[1024], command$[512], cmd$[16], param$[128];

  Rx$ = AnthemSTR.SocketRxBuf;  
  ClearBuffer(AnthemSTR.SocketRxBuf);

  command$ = left(Rx$, find(";", Rx$) - 1);
  firstChar = byte(command$, 1);

  trace("RECV: %s", command$);
                                
  switch(firstChar) {
    case (70):  //command starts with F (front panel)
      processFront(command$);
    case (73):  //command starts with I (internal)
      processInternal(command$);
    case (82):  //command starts with R (relay)
      processRelay(command$);
    case (83):  //command starts with S (speaker)
      processSpeaker(command$);
    case (90):  //command starts with Z (zone)
      processZone(command$);
    default:
      trace("unknown command: %s", command$);
  }              
}

SOCKETSTATUS AnthemSTR {
  signed_integer status;
  Status = SocketGetStatus();
  trace("Anthem connection status is %d", status);
}
*/

/*******************************************************************************************
  Main()
  Uncomment and place one-time startup code here
  (This code will get called when the system starts up)
*******************************************************************************************/
/*
Function Main()
{
    // TODO:  Add code here
    // Initialize declared global and local variables/arrays as needed.

    // WaitForInitializationComplete();
    // If you are reading any Input or Output variables, uncomment
    //   the WaitForInitializationComplete statement above and read
    //   them afterwards.  Input/Output variables will not have
    //   their correct values set until after the logic processor
    //   runs and propagates the values to them.
}
*/

