text: ///////////////////////////////////////////////////////////////////////////
  //
  // Copyright (c) 2000-2003 Intel Corporation 
  // All rights reserved. 
  //
  // Redistribution and use in source and binary forms, with or without 
  // modification, are permitted provided that the following conditions are met: 
  //
  // * Redistributions of source code must retain the above copyright notice, 
  // this list of conditions and the following disclaimer. 
  // * Redistributions in binary form must reproduce the above copyright notice, 
  // this list of conditions and the following disclaimer in the documentation 
  // and/or other materials provided with the distribution. 
  // * Neither name of Intel Corporation nor the names of its contributors 
  // may be used to endorse or promote products derived from this software 
  // without specific prior written permission.
  // 
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR 
  // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
  // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
  // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
  // OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
  // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  //
  ///////////////////////////////////////////////////////////////////////////
  
  #include "upnp_tv_device.h"
  
  #define DEFAULT_WEB_DIR "./web"
  
  #define DESC_URL_SIZE 200
  
  /*
     Device type for tv device 
   */
  extern char TvDeviceType[];
  
  /*
     Service types for tv services
   */
  extern char *TvServiceType[];
  
  /*
     Global arrays for storing Tv Control Service
     variable names, values, and defaults 
   */
  char *tvc_varname[] = { "Power", "Channel", "Volume" };
  char tvc_varval[TV_CONTROL_VARCOUNT][TV_MAX_VAL_LEN];
  char *tvc_varval_def[] = { "1", "1", "5" };
  
  /*
     Global arrays for storing Tv Picture Service
     variable names, values, and defaults 
   */
  char *tvp_varname[] = { "Color", "Tint", "Contrast", "Brightness" };
  char tvp_varval[TV_PICTURE_VARCOUNT][TV_MAX_VAL_LEN];
  char *tvp_varval_def[] = { "5", "5", "5", "5" };
  
  /*
     The amount of time (in seconds) before advertisements
     will expire 
   */
  int default_advr_expire = 100;
  
  /*
     Global structure for storing the state table for this device 
   */
  struct TvService tv_service_table[2];
  
  /*
     Device handle supplied by UPnP SDK 
   */
  UpnpDevice_Handle device_handle = -1;
  
  /*
     Mutex for protecting the global state table data
     in a multi-threaded, asynchronous environment.
     All functions should lock this mutex before reading
     or writing the state table data. 
   */
  ithread_mutex_t TVDevMutex;
  
  //Color constants
  #define MAX_COLOR 10
  #define MIN_COLOR 1
  
  //Brightness constants
  #define MAX_BRIGHTNESS 10
  #define MIN_BRIGHTNESS 1
  
  //Power constants
  #define POWER_ON 1
  #define POWER_OFF 0
  
  //Tint constants
  #define MAX_TINT 10
  #define MIN_TINT 1
  
  //Volume constants
  #define MAX_VOLUME 10
  #define MIN_VOLUME 1
  
  //Contrast constants
  #define MAX_CONTRAST 10
  #define MIN_CONTRAST 1
  
  //Channel constants
  #define MAX_CHANNEL 100
  #define MIN_CHANNEL 1
  
  /******************************************************************************
   * SetServiceTable
   *
   * Description: 
   *       Initializes the service table for the specified service.
   *       Note that 
   *       knowledge of the service description is
   *       assumed. 
   * Parameters:
   *   int serviceType - one of TV_SERVICE_CONTROL or, TV_SERVICE_PICTURE
   *   const char * UDN - UDN of device containing service
   *   const char * serviceId - serviceId of service
   *   const char * serviceTypeS - service type (as specified in Description
   *                                             Document) 
   *   struct TvService *out - service containing table to be set.
   *
   *****************************************************************************/
  int
  SetServiceTable( IN int serviceType,
                   IN const char *UDN,
                   IN const char *serviceId,
                   IN const char *serviceTypeS,
                   INOUT struct TvService *out )
  {
      unsigned int i = 0;
  
      strcpy( out->UDN, UDN );
      strcpy( out->ServiceId, serviceId );
      strcpy( out->ServiceType, serviceTypeS );
  
      switch ( serviceType ) {
          case TV_SERVICE_CONTROL:
              out->VariableCount = TV_CONTROL_VARCOUNT;
              for( i = 0;
                   i < tv_service_table[TV_SERVICE_CONTROL].VariableCount;
                   i++ ) {
                  tv_service_table[TV_SERVICE_CONTROL].VariableName[i]
                      = tvc_varname[i];
                  tv_service_table[TV_SERVICE_CONTROL].VariableStrVal[i]
                      = tvc_varval[i];
                  strcpy( tv_service_table[TV_SERVICE_CONTROL].
                          VariableStrVal[i], tvc_varval_def[i] );
              }
  
              break;
          case TV_SERVICE_PICTURE:
              out->VariableCount = TV_PICTURE_VARCOUNT;
  
              for( i = 0;
                   i < tv_service_table[TV_SERVICE_PICTURE].VariableCount;
                   i++ ) {
                  tv_service_table[TV_SERVICE_PICTURE].VariableName[i] =
                      tvp_varname[i];
                  tv_service_table[TV_SERVICE_PICTURE].VariableStrVal[i] =
                      tvp_varval[i];
                  strcpy( tv_service_table[TV_SERVICE_PICTURE].
                          VariableStrVal[i], tvp_varval_def[i] );
              }
  
              break;
          default:
              assert( 0 );
      }
  
      return SetActionTable( serviceType, out );
  
  }
  
  /******************************************************************************
   * SetActionTable
   *
   * Description: 
   *       Initializes the action table for the specified service.
   *       Note that 
   *       knowledge of the service description is
   *       assumed.  Action names are hardcoded.
   * Parameters:
   *   int serviceType - one of TV_SERVICE_CONTROL or, TV_SERVICE_PICTURE
   *   struct TvService *out - service containing action table to set.
   *
   *****************************************************************************/
  int
  SetActionTable( IN int serviceType,
                  INOUT struct TvService *out )
  {
      if( serviceType == TV_SERVICE_CONTROL ) {
          out->ActionNames[0] = "PowerOn";
          out->actions[0] = TvDevicePowerOn;
          out->ActionNames[1] = "PowerOff";
          out->actions[1] = TvDevicePowerOff;
          out->ActionNames[2] = "SetChannel";
          out->actions[2] = TvDeviceSetChannel;
          out->ActionNames[3] = "IncreaseChannel";
          out->actions[3] = TvDeviceIncreaseChannel;
          out->ActionNames[4] = "DecreaseChannel";
          out->actions[4] = TvDeviceDecreaseChannel;
          out->ActionNames[5] = "SetVolume";
          out->actions[5] = TvDeviceSetVolume;
          out->ActionNames[6] = "IncreaseVolume";
          out->actions[6] = TvDeviceIncreaseVolume;
          out->ActionNames[7] = "DecreaseVolume";
          out->actions[7] = TvDeviceDecreaseVolume;
          out->ActionNames[8] = NULL;
          return 1;
      } else if( serviceType == TV_SERVICE_PICTURE ) {
          out->ActionNames[0] = "SetColor";
          out->ActionNames[1] = "IncreaseColor";
          out->ActionNames[2] = "DecreaseColor";
          out->actions[0] = TvDeviceSetColor;
          out->actions[1] = TvDeviceIncreaseColor;
          out->actions[2] = TvDeviceDecreaseColor;
          out->ActionNames[3] = "SetTint";
          out->ActionNames[4] = "IncreaseTint";
          out->ActionNames[5] = "DecreaseTint";
          out->actions[3] = TvDeviceSetTint;
          out->actions[4] = TvDeviceIncreaseTint;
          out->actions[5] = TvDeviceDecreaseTint;
  
          out->ActionNames[6] = "SetBrightness";
          out->ActionNames[7] = "IncreaseBrightness";
          out->ActionNames[8] = "DecreaseBrightness";
          out->actions[6] = TvDeviceSetBrightness;
          out->actions[7] = TvDeviceIncreaseBrightness;
          out->actions[8] = TvDeviceDecreaseBrightness;
  
          out->ActionNames[9] = "SetContrast";
          out->ActionNames[10] = "IncreaseContrast";
          out->ActionNames[11] = "DecreaseContrast";
  
          out->actions[9] = TvDeviceSetContrast;
          out->actions[10] = TvDeviceIncreaseContrast;
          out->actions[11] = TvDeviceDecreaseContrast;
          return 1;
      }
  
      return 0;
  
  }
  
  /******************************************************************************
   * TvDeviceStateTableInit
   *
   * Description: 
   *       Initialize the device state table for 
   * 	 this TvDevice, pulling identifier info
   *       from the description Document.  Note that 
   *       knowledge of the service description is
   *       assumed.  State table variables and default
   *       values are currently hardcoded in this file
   *       rather than being read from service description
   *       documents.
   *
   * Parameters:
   *   DescDocURL -- The description document URL
   *
   *****************************************************************************/
  int
  TvDeviceStateTableInit( IN char *DescDocURL )
  {
      IXML_Document *DescDoc = NULL;
      int ret = UPNP_E_SUCCESS;
      char *servid_ctrl = NULL,
       *evnturl_ctrl = NULL,
       *ctrlurl_ctrl = NULL;
      char *servid_pict = NULL,
       *evnturl_pict = NULL,
       *ctrlurl_pict = NULL;
      char *udn = NULL;
  
      //Download description document
      if( UpnpDownloadXmlDoc( DescDocURL, &DescDoc ) != UPNP_E_SUCCESS ) {
          SampleUtil_Print( "TvDeviceStateTableInit -- Error Parsing %s\n",
                            DescDocURL );
          ret = UPNP_E_INVALID_DESC;
          goto error_handler;
      }
  
      udn = SampleUtil_GetFirstDocumentItem( DescDoc, "UDN" );
  
      /*
         Find the Tv Control Service identifiers 
       */
      if( !SampleUtil_FindAndParseService( DescDoc, DescDocURL,
                                           TvServiceType[TV_SERVICE_CONTROL],
                                           &servid_ctrl, &evnturl_ctrl,
                                           &ctrlurl_ctrl ) ) {
          SampleUtil_Print( "TvDeviceStateTableInit -- Error: Could not find"
                            " Service: %s\n",
                            TvServiceType[TV_SERVICE_CONTROL] );
  
          ret = UPNP_E_INVALID_DESC;
          goto error_handler;
      }
  
      //set control service table
      SetServiceTable( TV_SERVICE_CONTROL, udn, servid_ctrl,
                       TvServiceType[TV_SERVICE_CONTROL],
                       &tv_service_table[TV_SERVICE_CONTROL] );
  
      /*
         Find the Tv Picture Service identifiers 
       */
      if( !SampleUtil_FindAndParseService( DescDoc, DescDocURL,
                                           TvServiceType[TV_SERVICE_PICTURE],
                                           &servid_pict, &evnturl_pict,
                                           &ctrlurl_pict ) ) {
          SampleUtil_Print( "TvDeviceStateTableInit -- Error: Could not find"
                            " Service: %s\n",
                            TvServiceType[TV_SERVICE_PICTURE] );
  
          ret = UPNP_E_INVALID_DESC;
          goto error_handler;
      }
      //set picture service table
      SetServiceTable( TV_SERVICE_PICTURE, udn, servid_pict,
                       TvServiceType[TV_SERVICE_PICTURE],
                       &tv_service_table[TV_SERVICE_PICTURE] );
  
    error_handler:
  
      //clean up
      if( udn )
          free( udn );
      if( servid_ctrl )
          free( servid_ctrl );
      if( evnturl_ctrl )
          free( evnturl_ctrl );
      if( ctrlurl_ctrl )
          free( ctrlurl_ctrl );
      if( servid_pict )
          free( servid_pict );
      if( evnturl_pict )
          free( evnturl_pict );
      if( ctrlurl_pict )
          free( ctrlurl_pict );
      if( DescDoc )
          ixmlDocument_free( DescDoc );
  
      return ( ret );
  }
  
  /******************************************************************************
   * TvDeviceHandleSubscriptionRequest
   *
   * Description: 
   *       Called during a subscription request callback.  If the
   *       subscription request is for this device and either its
   *       control service or picture service, then accept it.
   *
   * Parameters:
   *   sr_event -- The subscription request event structure
   *
   *****************************************************************************/
  int
  TvDeviceHandleSubscriptionRequest( IN struct Upnp_Subscription_Request
                                     *sr_event )
  {
      unsigned int i = 0;         //,j=0;
  
      // IXML_Document *PropSet=NULL;
  
      //lock state mutex
      ithread_mutex_lock( &TVDevMutex );
  
      for( i = 0; i < TV_SERVICE_SERVCOUNT; i++ ) {
          if( ( strcmp( sr_event->UDN, tv_service_table[i].UDN ) == 0 ) &&
              ( strcmp( sr_event->ServiceId, tv_service_table[i].ServiceId )
                == 0 ) ) {
  
              /*
                 PropSet = NULL;
  
                 for (j=0; j< tv_service_table[i].VariableCount; j++)
                 {
                 //add each variable to the property set
                 //for initial state dump
                 UpnpAddToPropertySet(&PropSet, 
                 tv_service_table[i].VariableName[j],
                 tv_service_table[i].VariableStrVal[j]);
                 }
  
                 //dump initial state 
                 UpnpAcceptSubscriptionExt(device_handle, sr_event->UDN, 
                 sr_event->ServiceId,
                 PropSet,sr_event->Sid);
                 //free document
                 Document_free(PropSet);
  
               */
  
              UpnpAcceptSubscription( device_handle,
                                      sr_event->UDN,
                                      sr_event->ServiceId,
                                      ( const char ** )tv_service_table[i].
                                      VariableName,
                                      ( const char ** )tv_service_table[i].
                                      VariableStrVal,
                                      tv_service_table[i].VariableCount,
                                      sr_event->Sid );
  
          }
      }
  
      ithread_mutex_unlock( &TVDevMutex );
  
      return ( 1 );
  }
  
  /******************************************************************************
   * TvDeviceHandleGetVarRequest
   *
   * Description: 
   *       Called during a get variable request callback.  If the
   *       request is for this device and either its control service
   *       or picture service, then respond with the variable value.
   *
   * Parameters:
   *   cgv_event -- The control get variable request event structure
   *
   *****************************************************************************/
  int
  TvDeviceHandleGetVarRequest( INOUT struct Upnp_State_Var_Request
                               *cgv_event )
  {
      unsigned int i = 0,
        j = 0;
      int getvar_succeeded = 0;
  
      cgv_event->CurrentVal = NULL;
  
      ithread_mutex_lock( &TVDevMutex );
  
      for( i = 0; i < TV_SERVICE_SERVCOUNT; i++ ) {
          //check udn and service id
          if( ( strcmp( cgv_event->DevUDN, tv_service_table[i].UDN ) == 0 )
              &&
              ( strcmp( cgv_event->ServiceID, tv_service_table[i].ServiceId )
                == 0 ) ) {
              //check variable name
              for( j = 0; j < tv_service_table[i].VariableCount; j++ ) {
                  if( strcmp( cgv_event->StateVarName,
                              tv_service_table[i].VariableName[j] ) == 0 ) {
                      getvar_succeeded = 1;
                      cgv_event->CurrentVal =
                          ixmlCloneDOMString( tv_service_table[i].
                                              VariableStrVal[j] );
                      break;
                  }
              }
          }
      }
  
      if( getvar_succeeded ) {
          cgv_event->ErrCode = UPNP_E_SUCCESS;
      } else {
          SampleUtil_Print
              ( "Error in UPNP_CONTROL_GET_VAR_REQUEST callback:\n" );
          SampleUtil_Print( "   Unknown variable name = %s\n",
                            cgv_event->StateVarName );
          cgv_event->ErrCode = 404;
          strcpy( cgv_event->ErrStr, "Invalid Variable" );
      }
  
      ithread_mutex_unlock( &TVDevMutex );
  
      return ( cgv_event->ErrCode == UPNP_E_SUCCESS );
  }
  
  /******************************************************************************
   * TvDeviceHandleActionRequest
   *
   * Description: 
   *       Called during an action request callback.  If the
   *       request is for this device and either its control service
   *       or picture service, then perform the action and respond.
   *
   * Parameters:
   *   ca_event -- The control action request event structure
   *
   *****************************************************************************/
  int
  TvDeviceHandleActionRequest( INOUT struct Upnp_Action_Request *ca_event )
  {
  
      /*
         Defaults if action not found 
       */
      int action_found = 0;
      int i = 0;
      int service = -1;
      int retCode = 0;
      char *errorString = NULL;
  
      ca_event->ErrCode = 0;
      ca_event->ActionResult = NULL;
  
      if( ( strcmp( ca_event->DevUDN,
                    tv_service_table[TV_SERVICE_CONTROL].UDN ) == 0 ) &&
          ( strcmp
            ( ca_event->ServiceID,
              tv_service_table[TV_SERVICE_CONTROL].ServiceId ) == 0 ) ) {
          /*
             Request for action in the TvDevice Control Service 
           */
          service = TV_SERVICE_CONTROL;
      } else if( ( strcmp( ca_event->DevUDN,
                           tv_service_table[TV_SERVICE_PICTURE].UDN ) == 0 )
                 &&
                 ( strcmp
                   ( ca_event->ServiceID,
                     tv_service_table[TV_SERVICE_PICTURE].ServiceId ) ==
                   0 ) ) {
          /*
             Request for action in the TvDevice Picture Service 
           */
          service = TV_SERVICE_PICTURE;
      }
      //Find and call appropriate procedure based on action name
      //Each action name has an associated procedure stored in the
      //service table. These are set at initialization.
  
      for( i = 0; ( ( i < TV_MAXACTIONS ) &&
                    ( tv_service_table[service].ActionNames[i] != NULL ) );
           i++ ) {
  
          if( !strcmp( ca_event->ActionName,
                       tv_service_table[service].ActionNames[i] ) ) {
  
              if( ( !strcmp( tv_service_table[TV_SERVICE_CONTROL].
                             VariableStrVal[TV_CONTROL_POWER], "1" ) )
                  || ( !strcmp( ca_event->ActionName, "PowerOn" ) ) ) {
                  retCode =
                      tv_service_table[service].actions[i] ( ca_event->
                                                             ActionRequest,
                                                             &ca_event->
                                                             ActionResult,
                                                             &errorString );
              } else {
                  errorString = "Power is Off";
                  retCode = UPNP_E_INTERNAL_ERROR;
              }
              action_found = 1;
              break;
          }
      }
  
      if( !action_found ) {
          ca_event->ActionResult = NULL;
          strcpy( ca_event->ErrStr, "Invalid Action" );
          ca_event->ErrCode = 401;
      } else {
          if( retCode == UPNP_E_SUCCESS ) {
              ca_event->ErrCode = UPNP_E_SUCCESS;
          } else {
              //copy the error string 
              strcpy( ca_event->ErrStr, errorString );
              switch ( retCode ) {
                  case UPNP_E_INVALID_PARAM:
                      {
                          ca_event->ErrCode = 402;
                          break;
                      }
                  case UPNP_E_INTERNAL_ERROR:
                  default:
                      {
                          ca_event->ErrCode = 501;
                          break;
                      }
  
              }
          }
      }
  
      return ( ca_event->ErrCode );
  }
  
  /******************************************************************************
   * TvDeviceSetServiceTableVar
   *
   * Description: 
   *       Update the TvDevice service state table, and notify all subscribed 
   *       control points of the updated state.  Note that since this function
   *       blocks on the mutex TVDevMutex, to avoid a hang this function should 
   *       not be called within any other function that currently has this mutex 
   *       locked.
   *
   * Parameters:
   *   service -- The service number (TV_SERVICE_CONTROL or TV_SERVICE_PICTURE)
   *   variable -- The variable number (TV_CONTROL_POWER, TV_CONTROL_CHANNEL,
   *                   TV_CONTROL_VOLUME, TV_PICTURE_COLOR, TV_PICTURE_TINT,
   *                   TV_PICTURE_CONTRAST, or TV_PICTURE_BRIGHTNESS)
   *   value -- The string representation of the new value
   *
   *****************************************************************************/
  int
  TvDeviceSetServiceTableVar( IN unsigned int service,
                              IN unsigned int variable,
                              IN char *value )
  {
      //IXML_Document  *PropSet= NULL;
  
      if( ( service >= TV_SERVICE_SERVCOUNT )
          || ( variable >= tv_service_table[service].VariableCount )
          || ( strlen( value ) >= TV_MAX_VAL_LEN ) ) {
          return ( 0 );
      }
  
      ithread_mutex_lock( &TVDevMutex );
  
      strcpy( tv_service_table[service].VariableStrVal[variable], value );
  
      /*
         //Using utility api
         PropSet= UpnpCreatePropertySet(1,tv_service_table[service].
         VariableName[variable], 
         tv_service_table[service].
         VariableStrVal[variable]);
  
         UpnpNotifyExt(device_handle, tv_service_table[service].UDN, 
         tv_service_table[service].ServiceId,PropSet);
  
         //Free created property set
         Document_free(PropSet);
       */
  
      UpnpNotify( device_handle,
                  tv_service_table[service].UDN,
                  tv_service_table[service].ServiceId,
                  ( const char ** )&tv_service_table[service].
                  VariableName[variable],
                  ( const char ** )&tv_service_table[service].
                  VariableStrVal[variable], 1 );
  
      ithread_mutex_unlock( &TVDevMutex );
  
      return ( 1 );
  
  }
  
  /******************************************************************************
   * TvDeviceSetPower
   *
   * Description: 
   *       Turn the power on/off, update the TvDevice control service
   *       state table, and notify all subscribed control points of the
   *       updated state.
   *
   * Parameters:
   *   on -- If 1, turn power on.  If 0, turn power off.
   *
   *****************************************************************************/
  int
  TvDeviceSetPower( IN int on )
  {
      char value[TV_MAX_VAL_LEN];
      int ret = 0;
  
      if( on != POWER_ON && on != POWER_OFF ) {
          SampleUtil_Print( "error: can't set power to value %d\n", on );
          return ( 0 );
      }
  
      /*
         Vendor-specific code to turn the power on/off goes here 
       */
  
      sprintf( value, "%d", on );
      ret = TvDeviceSetServiceTableVar( TV_SERVICE_CONTROL, TV_CONTROL_POWER,
                                        value );
  
      return ( ret );
  }
  
  /******************************************************************************
   * TvDevicePowerOn
   *
   * Description: 
   *       Turn the power on.
   *
   * Parameters:
   *
   *    IXML_Document * in - document of action request
   *    IXML_Document **out - action result
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDevicePowerOn( IN IXML_Document * in,
                   OUT IXML_Document ** out,
                   OUT char **errorString )
  {
      ( *out ) = NULL;
      ( *errorString ) = NULL;
  
      if( TvDeviceSetPower( POWER_ON ) ) {
          //create a response
  
          if( UpnpAddToActionResponse( out, "PowerOn",
                                       TvServiceType[TV_SERVICE_CONTROL],
                                       "Power", "1" ) != UPNP_E_SUCCESS ) {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              return UPNP_E_INTERNAL_ERROR;
          }
          return UPNP_E_SUCCESS;
      } else {
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  
  }
  
  /******************************************************************************
   * TvDevicePowerOff
   *
   * Description: 
   *       Turn the power off.
   *
   * Parameters:
   *    
   *    IXML_Document * in - document of action request
   *    IXML_Document **out - action result
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDevicePowerOff( IN IXML_Document * in,
                    OUT IXML_Document ** out,
                    OUT char **errorString )
  {
      ( *out ) = NULL;
      ( *errorString ) = NULL;
      if( TvDeviceSetPower( POWER_OFF ) ) {
          //create a response
  
          if( UpnpAddToActionResponse( out, "PowerOff",
                                       TvServiceType[TV_SERVICE_CONTROL],
                                       "Power", "0" ) != UPNP_E_SUCCESS ) {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              return UPNP_E_INTERNAL_ERROR;
          }
  
          return UPNP_E_SUCCESS;
      }
  
      ( *errorString ) = "Internal Error";
      return UPNP_E_INTERNAL_ERROR;
  }
  
  /******************************************************************************
   * TvDeviceSetChannel
   *
   * Description: 
   *       Change the channel, update the TvDevice control service
   *       state table, and notify all subscribed control points of the
   *       updated state.
   *
   * Parameters:
   *    
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceSetChannel( IN IXML_Document * in,
                      OUT IXML_Document ** out,
                      OUT char **errorString )
  {
  
      char *value = NULL;
  
      int channel = 0;
  
      ( *out ) = NULL;
      ( *errorString ) = NULL;
  
      if( !( value = SampleUtil_GetFirstDocumentItem( in, "Channel" ) ) ) {
          ( *errorString ) = "Invalid Channel";
          return UPNP_E_INVALID_PARAM;
      }
  
      channel = atoi( value );
  
      if( channel < MIN_CHANNEL || channel > MAX_CHANNEL ) {
  
          free( value );
          SampleUtil_Print( "error: can't change to channel %d\n", channel );
          ( *errorString ) = "Invalid Channel";
          return UPNP_E_INVALID_PARAM;
      }
  
      /*
         Vendor-specific code to set the channel goes here 
       */
  
      if( TvDeviceSetServiceTableVar( TV_SERVICE_CONTROL,
                                      TV_CONTROL_CHANNEL, value ) ) {
          if( UpnpAddToActionResponse( out, "SetChannel",
                                       TvServiceType[TV_SERVICE_CONTROL],
                                       "NewChannel",
                                       value ) != UPNP_E_SUCCESS ) {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              free( value );
              return UPNP_E_INTERNAL_ERROR;
          }
          free( value );
          return UPNP_E_SUCCESS;
      } else {
          free( value );
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  
  }
  
  /******************************************************************************
   * IncrementChannel
   *
   * Description: 
   *       Increment the channel.  Read the current channel from the state
   *       table, add the increment, and then change the channel.
   *
   * Parameters:
   *   incr -- The increment by which to change the channel.
   *      
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *****************************************************************************/
  int
  IncrementChannel( IN int incr,
                    IN IXML_Document * in,
                    OUT IXML_Document ** out,
                    OUT char **errorString )
  {
      int curchannel,
        newchannel;
  
      char *actionName = NULL;
      char value[TV_MAX_VAL_LEN];
  
      if( incr > 0 ) {
          actionName = "IncreaseChannel";
      } else {
          actionName = "DecreaseChannel";
      }
  
      ithread_mutex_lock( &TVDevMutex );
      curchannel = atoi( tv_service_table[TV_SERVICE_CONTROL].
                         VariableStrVal[TV_CONTROL_CHANNEL] );
      ithread_mutex_unlock( &TVDevMutex );
  
      newchannel = curchannel + incr;
  
      if( newchannel < MIN_CHANNEL || newchannel > MAX_CHANNEL ) {
          SampleUtil_Print( "error: can't change to channel %d\n",
                            newchannel );
          ( *errorString ) = "Invalid Channel";
          return UPNP_E_INVALID_PARAM;
      }
  
      /*
         Vendor-specific code to set the channel goes here 
       */
  
      sprintf( value, "%d", newchannel );
  
      if( TvDeviceSetServiceTableVar( TV_SERVICE_CONTROL,
                                      TV_CONTROL_CHANNEL, value ) ) {
          if( UpnpAddToActionResponse( out, actionName,
                                       TvServiceType[TV_SERVICE_CONTROL],
                                       "Channel", value ) != UPNP_E_SUCCESS )
          {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              return UPNP_E_INTERNAL_ERROR;
          }
          return UPNP_E_SUCCESS;
      } else {
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  }
  
  /******************************************************************************
   * TvDeviceDecreaseChannel
   *
   * Description: 
   *       Decrease the channel.  
   *
   * Parameters:
   *   
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceDecreaseChannel( IN IXML_Document * in,
                           OUT IXML_Document ** out,
                           OUT char **errorString )
  {
      return IncrementChannel( -1, in, out, errorString );
  
  }
  
  /******************************************************************************
   * TvDeviceIncreaseChannel
   *
   * Description: 
   *       Increase the channel.  
   *
   * Parameters:
   *   
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceIncreaseChannel( IN IXML_Document * in,
                           OUT IXML_Document ** out,
                           OUT char **errorString )
  {
      return IncrementChannel( 1, in, out, errorString );
  
  }
  
  /******************************************************************************
   * TvDeviceSetVolume
   *
   * Description: 
   *       Change the volume, update the TvDevice control service
   *       state table, and notify all subscribed control points of the
   *       updated state.
   *
   * Parameters:
   *  
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceSetVolume( IN IXML_Document * in,
                     OUT IXML_Document ** out,
                     OUT char **errorString )
  {
  
      char *value = NULL;
  
      int volume = 0;
  
      ( *out ) = NULL;
      ( *errorString ) = NULL;
  
      if( !( value = SampleUtil_GetFirstDocumentItem( in, "Volume" ) ) ) {
          ( *errorString ) = "Invalid Volume";
          return UPNP_E_INVALID_PARAM;
      }
  
      volume = atoi( value );
  
      if( volume < MIN_VOLUME || volume > MAX_VOLUME ) {
          SampleUtil_Print( "error: can't change to volume %d\n", volume );
          ( *errorString ) = "Invalid Volume";
          return UPNP_E_INVALID_PARAM;
      }
  
      /*
         Vendor-specific code to set the volume goes here 
       */
  
      if( TvDeviceSetServiceTableVar( TV_SERVICE_CONTROL,
                                      TV_CONTROL_VOLUME, value ) ) {
          if( UpnpAddToActionResponse( out, "SetVolume",
                                       TvServiceType[TV_SERVICE_CONTROL],
                                       "NewVolume",
                                       value ) != UPNP_E_SUCCESS ) {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              free( value );
              return UPNP_E_INTERNAL_ERROR;
          }
          free( value );
          return UPNP_E_SUCCESS;
      } else {
          free( value );
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  
  }
  
  /******************************************************************************
   * IncrementVolume
   *
   * Description: 
   *       Increment the volume.  Read the current volume from the state
   *       table, add the increment, and then change the volume.
   *
   * Parameters:
   *   incr -- The increment by which to change the volume.
   *      
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  IncrementVolume( IN int incr,
                   IN IXML_Document * in,
                   OUT IXML_Document ** out,
                   OUT char **errorString )
  {
      int curvolume,
        newvolume;
      char *actionName = NULL;
      char value[TV_MAX_VAL_LEN];
  
      if( incr > 0 ) {
          actionName = "IncreaseVolume";
      } else {
          actionName = "DecreaseVolume";
      }
  
      ithread_mutex_lock( &TVDevMutex );
      curvolume = atoi( tv_service_table[TV_SERVICE_CONTROL].
                        VariableStrVal[TV_CONTROL_VOLUME] );
      ithread_mutex_unlock( &TVDevMutex );
  
      newvolume = curvolume + incr;
  
      if( newvolume < MIN_VOLUME || newvolume > MAX_VOLUME ) {
          SampleUtil_Print( "error: can't change to volume %d\n",
                            newvolume );
          ( *errorString ) = "Invalid Volume";
          return UPNP_E_INVALID_PARAM;
      }
  
      /*
         Vendor-specific code to set the channel goes here 
       */
  
      sprintf( value, "%d", newvolume );
  
      if( TvDeviceSetServiceTableVar( TV_SERVICE_CONTROL,
                                      TV_CONTROL_VOLUME, value ) ) {
          if( UpnpAddToActionResponse( out, actionName,
                                       TvServiceType[TV_SERVICE_CONTROL],
                                       "Volume", value ) != UPNP_E_SUCCESS )
          {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              return UPNP_E_INTERNAL_ERROR;
          }
          return UPNP_E_SUCCESS;
      } else {
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  
  }
  
  /******************************************************************************
   * TvDeviceIncrVolume
   *
   * Description: 
   *       Increase the volume. 
   *
   * Parameters:
   *   
   *
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *****************************************************************************/
  int
  TvDeviceIncreaseVolume( IN IXML_Document * in,
                          OUT IXML_Document ** out,
                          OUT char **errorString )
  {
  
      return IncrementVolume( 1, in, out, errorString );
  
  }
  
  /******************************************************************************
   * TvDeviceDecreaseVolume
   *
   * Description: 
   *       Decrease the volume.
   *
   * Parameters:
   *   
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceDecreaseVolume( IN IXML_Document * in,
                          OUT IXML_Document ** out,
                          OUT char **errorString )
  {
  
      return IncrementVolume( -1, in, out, errorString );
  
  }
  
  /******************************************************************************
   * TvDeviceSetColor
   *
   * Description: 
   *       Change the color, update the TvDevice picture service
   *       state table, and notify all subscribed control points of the
   *       updated state.
   *
   * Parameters:
   *   
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceSetColor( IN IXML_Document * in,
                    OUT IXML_Document ** out,
                    OUT char **errorString )
  {
  
      char *value = NULL;
  
      int color = 0;
  
      ( *out ) = NULL;
      ( *errorString ) = NULL;
      if( !( value = SampleUtil_GetFirstDocumentItem( in, "Color" ) ) ) {
          ( *errorString ) = "Invalid Color";
          return UPNP_E_INVALID_PARAM;
      }
  
      color = atoi( value );
  
      if( color < MIN_COLOR || color > MAX_COLOR ) {
          SampleUtil_Print( "error: can't change to color %d\n", color );
          ( *errorString ) = "Invalid Color";
          return UPNP_E_INVALID_PARAM;
      }
  
      /*
         Vendor-specific code to set the volume goes here 
       */
  
      if( TvDeviceSetServiceTableVar( TV_SERVICE_PICTURE,
                                      TV_PICTURE_COLOR, value ) ) {
          if( UpnpAddToActionResponse( out, "SetColor",
                                       TvServiceType[TV_SERVICE_PICTURE],
                                       "NewColor",
                                       value ) != UPNP_E_SUCCESS ) {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              free( value );
              return UPNP_E_INTERNAL_ERROR;
          }
          free( value );
          return UPNP_E_SUCCESS;
      } else {
          free( value );
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  
  }
  
  /******************************************************************************
   * IncrementColor
   *
   * Description: 
   *       Increment the color.  Read the current color from the state
   *       table, add the increment, and then change the color.
   *
   * Parameters:
   *   incr -- The increment by which to change the color.
   *   
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *****************************************************************************/
  
  int
  IncrementColor( IN int incr,
                  IN IXML_Document * in,
                  OUT IXML_Document ** out,
                  OUT char **errorString )
  {
      int curcolor,
        newcolor;
  
      char *actionName;
      char value[TV_MAX_VAL_LEN];
  
      if( incr > 0 ) {
          actionName = "IncreaseColor";
      } else {
          actionName = "DecreaseColor";
      }
  
      ithread_mutex_lock( &TVDevMutex );
      curcolor = atoi( tv_service_table[TV_SERVICE_PICTURE].
                       VariableStrVal[TV_PICTURE_COLOR] );
      ithread_mutex_unlock( &TVDevMutex );
  
      newcolor = curcolor + incr;
  
      if( newcolor < MIN_COLOR || newcolor > MAX_COLOR ) {
          SampleUtil_Print( "error: can't change to color %d\n", newcolor );
          ( *errorString ) = "Invalid Color";
          return UPNP_E_INVALID_PARAM;
      }
  
      /*
         Vendor-specific code to set the channel goes here 
       */
  
      sprintf( value, "%d", newcolor );
  
      if( TvDeviceSetServiceTableVar( TV_SERVICE_PICTURE,
                                      TV_PICTURE_COLOR, value ) ) {
          if( UpnpAddToActionResponse( out, actionName,
                                       TvServiceType[TV_SERVICE_PICTURE],
                                       "Color", value ) != UPNP_E_SUCCESS ) {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              return UPNP_E_INTERNAL_ERROR;
          }
          return UPNP_E_SUCCESS;
      } else {
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  }
  
  /******************************************************************************
   * TvDeviceDecreaseColor
   *
   * Description: 
   *       Decrease the color.  
   *
   * Parameters:
   *   
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *****************************************************************************/
  int
  TvDeviceDecreaseColor( IN IXML_Document * in,
                         OUT IXML_Document ** out,
                         OUT char **errorString )
  {
  
      return IncrementColor( -1, in, out, errorString );
  }
  
  /******************************************************************************
   * TvDeviceIncreaseColor
   *
   * Description: 
   *       Increase the color.
   *
   * Parameters:
   *
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *****************************************************************************/
  int
  TvDeviceIncreaseColor( IN IXML_Document * in,
                         OUT IXML_Document ** out,
                         OUT char **errorString )
  {
  
      return IncrementColor( 1, in, out, errorString );
  }
  
  /******************************************************************************
   * TvDeviceSetTint
   *
   * Description: 
   *       Change the tint, update the TvDevice picture service
   *       state table, and notify all subscribed control points of the
   *       updated state.
   *
   * Parameters:
   *
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceSetTint( IN IXML_Document * in,
                   OUT IXML_Document ** out,
                   OUT char **errorString )
  {
  
      char *value = NULL;
  
      int tint = -1;
  
      ( *out ) = NULL;
      ( *errorString ) = NULL;
  
      if( !( value = SampleUtil_GetFirstDocumentItem( in, "Tint" ) ) ) {
          ( *errorString ) = "Invalid Tint";
          return UPNP_E_INVALID_PARAM;
      }
  
      tint = atoi( value );
  
      if( tint < MIN_TINT || tint > MAX_TINT ) {
          SampleUtil_Print( "error: can't change to tint %d\n", tint );
          ( *errorString ) = "Invalid Tint";
          return UPNP_E_INVALID_PARAM;
      }
  
      /*
         Vendor-specific code to set the volume goes here 
       */
  
      if( TvDeviceSetServiceTableVar( TV_SERVICE_PICTURE,
                                      TV_PICTURE_TINT, value ) ) {
          if( UpnpAddToActionResponse( out, "SetTint",
                                       TvServiceType[TV_SERVICE_PICTURE],
                                       "NewTint", value ) != UPNP_E_SUCCESS )
          {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              free( value );
              return UPNP_E_INTERNAL_ERROR;
          }
          free( value );
          return UPNP_E_SUCCESS;
      } else {
          free( value );
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  
  }
  
  /******************************************************************************
   * IncrementTint
   *
   * Description: 
   *       Increment the tint.  Read the current tint from the state
   *       table, add the increment, and then change the tint.
   *
   * Parameters:
   *   incr -- The increment by which to change the tint.
   *   
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *****************************************************************************/
  int
  IncrementTint( IN int incr,
                 IN IXML_Document * in,
                 OUT IXML_Document ** out,
                 OUT char **errorString )
  {
      int curtint,
        newtint;
  
      char *actionName = NULL;
      char value[TV_MAX_VAL_LEN];
  
      if( incr > 0 ) {
          actionName = "IncreaseTint";
      } else {
          actionName = "DecreaseTint";
      }
  
      ithread_mutex_lock( &TVDevMutex );
      curtint = atoi( tv_service_table[TV_SERVICE_PICTURE].
                      VariableStrVal[TV_PICTURE_TINT] );
      ithread_mutex_unlock( &TVDevMutex );
  
      newtint = curtint + incr;
  
      if( newtint < MIN_TINT || newtint > MAX_TINT ) {
          SampleUtil_Print( "error: can't change to tint %d\n", newtint );
          ( *errorString ) = "Invalid Tint";
          return UPNP_E_INVALID_PARAM;
      }
  
      /*
         Vendor-specific code to set the channel goes here 
       */
  
      sprintf( value, "%d", newtint );
  
      if( TvDeviceSetServiceTableVar( TV_SERVICE_PICTURE,
                                      TV_PICTURE_TINT, value ) ) {
          if( UpnpAddToActionResponse( out, actionName,
                                       TvServiceType[TV_SERVICE_PICTURE],
                                       "Tint", value ) != UPNP_E_SUCCESS ) {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              return UPNP_E_INTERNAL_ERROR;
          }
          return UPNP_E_SUCCESS;
      } else {
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  
  }
  
  /******************************************************************************
   * TvDeviceIncreaseTint
   *
   * Description: 
   *       Increase tint.
   *
   * Parameters:
   *   
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceIncreaseTint( IN IXML_Document * in,
                        OUT IXML_Document ** out,
                        OUT char **errorString )
  {
  
      return IncrementTint( 1, in, out, errorString );
  }
  
  /******************************************************************************
   * TvDeviceDecreaseTint
   *
   * Description: 
   *       Decrease tint.
   *
   * Parameters:
   *  
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceDecreaseTint( IN IXML_Document * in,
                        OUT IXML_Document ** out,
                        OUT char **errorString )
  {
  
      return IncrementTint( -1, in, out, errorString );
  }
  
  /*****************************************************************************
   * TvDeviceSetContrast
   *
   * Description: 
   *       Change the contrast, update the TvDevice picture service
   *       state table, and notify all subscribed control points of the
   *       updated state.
   *
   * Parameters:
   *   
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   ****************************************************************************/
  int
  TvDeviceSetContrast( IN IXML_Document * in,
                       OUT IXML_Document ** out,
                       OUT char **errorString )
  {
  
      char *value = NULL;
      int contrast = -1;
  
      ( *out ) = NULL;
      ( *errorString ) = NULL;
  
      if( !( value = SampleUtil_GetFirstDocumentItem( in, "Contrast" ) ) ) {
          ( *errorString ) = "Invalid Contrast";
          return UPNP_E_INVALID_PARAM;
      }
  
      contrast = atoi( value );
  
      if( contrast < MIN_CONTRAST || contrast > MAX_CONTRAST ) {
          SampleUtil_Print( "error: can't change to contrast %d\n",
                            contrast );
          ( *errorString ) = "Invalid Contrast";
          return UPNP_E_INVALID_PARAM;
      }
  
      /*
         Vendor-specific code to set the volume goes here 
       */
  
      if( TvDeviceSetServiceTableVar( TV_SERVICE_PICTURE,
                                      TV_PICTURE_CONTRAST, value ) ) {
          if( UpnpAddToActionResponse( out, "SetContrast",
                                       TvServiceType[TV_SERVICE_PICTURE],
                                       "NewContrast",
                                       value ) != UPNP_E_SUCCESS ) {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              free( value );
              return UPNP_E_INTERNAL_ERROR;
          }
          free( value );
          return UPNP_E_SUCCESS;
      } else {
          free( value );
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  
  }
  
  /******************************************************************************
   * IncrementContrast
   *
   * Description: 
   *       Increment the contrast.  Read the current contrast from the state
   *       table, add the increment, and then change the contrast.
   *
   * Parameters:
   *   incr -- The increment by which to change the contrast.
   *   
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *****************************************************************************/
  int
  IncrementContrast( IN int incr,
                     IN IXML_Document * in,
                     OUT IXML_Document ** out,
                     OUT char **errorString )
  {
      int curcontrast,
        newcontrast;
  
      char *actionName = NULL;
      char value[TV_MAX_VAL_LEN];
  
      if( incr > 0 ) {
          actionName = "IncreaseContrast";
      } else {
          actionName = "DecreaseContrast";
      }
  
      ithread_mutex_lock( &TVDevMutex );
      curcontrast = atoi( tv_service_table[TV_SERVICE_PICTURE].
                          VariableStrVal[TV_PICTURE_CONTRAST] );
      ithread_mutex_unlock( &TVDevMutex );
  
      newcontrast = curcontrast + incr;
  
      if( newcontrast < MIN_CONTRAST || newcontrast > MAX_CONTRAST ) {
          SampleUtil_Print( "error: can't change to contrast %d\n",
                            newcontrast );
          ( *errorString ) = "Invalid Contrast";
          return UPNP_E_INVALID_PARAM;
      }
  
      /*
         Vendor-specific code to set the channel goes here 
       */
  
      sprintf( value, "%d", newcontrast );
  
      if( TvDeviceSetServiceTableVar( TV_SERVICE_PICTURE,
                                      TV_PICTURE_CONTRAST, value ) ) {
          if( UpnpAddToActionResponse( out, actionName,
                                       TvServiceType[TV_SERVICE_PICTURE],
                                       "Contrast",
                                       value ) != UPNP_E_SUCCESS ) {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              return UPNP_E_INTERNAL_ERROR;
          }
          return UPNP_E_SUCCESS;
      } else {
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  }
  
  /******************************************************************************
   * TvDeviceIncreaseContrast
   *
   * Description: 
   *
   *      Increase the contrast.
   *
   * Parameters:
   *       
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceIncreaseContrast( IN IXML_Document * in,
                            OUT IXML_Document ** out,
                            OUT char **errorString )
  {
  
      return IncrementContrast( 1, in, out, errorString );
  }
  
  /******************************************************************************
   * TvDeviceDecreaseContrast
   *
   * Description: 
   *      Decrease the contrast.
   *
   * Parameters:
   *          
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceDecreaseContrast( IXML_Document * in,
                            IXML_Document ** out,
                            char **errorString )
  {
      return IncrementContrast( -1, in, out, errorString );
  }
  
  /******************************************************************************
   * TvDeviceSetBrightness
   *
   * Description: 
   *       Change the brightness, update the TvDevice picture service
   *       state table, and notify all subscribed control points of the
   *       updated state.
   *
   * Parameters:
   *   brightness -- The brightness value to change to.
   *
   *****************************************************************************/
  int
  TvDeviceSetBrightness( IN IXML_Document * in,
                         OUT IXML_Document ** out,
                         OUT char **errorString )
  {
  
      char *value = NULL;
      int brightness = -1;
  
      ( *out ) = NULL;
      ( *errorString ) = NULL;
  
      if( !( value = SampleUtil_GetFirstDocumentItem( in, "Brightness" ) ) ) {
          ( *errorString ) = "Invalid Brightness";
          return UPNP_E_INVALID_PARAM;
      }
  
      brightness = atoi( value );
  
      if( brightness < MIN_BRIGHTNESS || brightness > MAX_BRIGHTNESS ) {
          SampleUtil_Print( "error: can't change to brightness %d\n",
                            brightness );
          ( *errorString ) = "Invalid Brightness";
          return UPNP_E_INVALID_PARAM;
      }
  
      /*
         Vendor-specific code to set the volume goes here 
       */
  
      if( TvDeviceSetServiceTableVar( TV_SERVICE_PICTURE,
                                      TV_PICTURE_BRIGHTNESS, value ) ) {
          if( UpnpAddToActionResponse( out, "SetBrightness",
                                       TvServiceType[TV_SERVICE_PICTURE],
                                       "NewBrightness",
                                       value ) != UPNP_E_SUCCESS ) {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              free( value );
              return UPNP_E_INTERNAL_ERROR;
          }
          free( value );
          return UPNP_E_SUCCESS;
      } else {
          free( value );
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  
  }
  
  /******************************************************************************
   * IncrementBrightness
   *
   * Description: 
   *       Increment the brightness.  Read the current brightness from the state
   *       table, add the increment, and then change the brightness.
   *
   * Parameters:
   *   incr -- The increment by which to change the brightness.
   *   
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *****************************************************************************/
  int
  IncrementBrightness( IN int incr,
                       IN IXML_Document * in,
                       OUT IXML_Document ** out,
                       OUT char **errorString )
  {
      int curbrightness,
        newbrightness;
      char *actionName = NULL;
      char value[TV_MAX_VAL_LEN];
  
      if( incr > 0 ) {
          actionName = "IncreaseBrightness";
      } else {
          actionName = "DecreaseBrightness";
      }
  
      ithread_mutex_lock( &TVDevMutex );
      curbrightness = atoi( tv_service_table[TV_SERVICE_PICTURE].
                            VariableStrVal[TV_PICTURE_BRIGHTNESS] );
      ithread_mutex_unlock( &TVDevMutex );
  
      newbrightness = curbrightness + incr;
  
      if( newbrightness < MIN_BRIGHTNESS || newbrightness > MAX_BRIGHTNESS ) {
          SampleUtil_Print( "error: can't change to brightness %d\n",
                            newbrightness );
          ( *errorString ) = "Invalid Brightness";
          return UPNP_E_INVALID_PARAM;
      }
  
      /*
         Vendor-specific code to set the channel goes here 
       */
  
      sprintf( value, "%d", newbrightness );
  
      if( TvDeviceSetServiceTableVar( TV_SERVICE_PICTURE,
                                      TV_PICTURE_BRIGHTNESS, value ) ) {
          if( UpnpAddToActionResponse( out, actionName,
                                       TvServiceType[TV_SERVICE_PICTURE],
                                       "Brightness",
                                       value ) != UPNP_E_SUCCESS ) {
              ( *out ) = NULL;
              ( *errorString ) = "Internal Error";
              return UPNP_E_INTERNAL_ERROR;
          }
          return UPNP_E_SUCCESS;
      } else {
          ( *errorString ) = "Internal Error";
          return UPNP_E_INTERNAL_ERROR;
      }
  }
  
  /******************************************************************************
   * TvDeviceIncreaseBrightness
   *
   * Description: 
   *       Increase brightness.
   *
   * Parameters:
   *
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceIncreaseBrightness( IN IXML_Document * in,
                              OUT IXML_Document ** out,
                              OUT char **errorString )
  {
      return IncrementBrightness( 1, in, out, errorString );
  }
  
  /******************************************************************************
   * TvDeviceDecreaseBrightness
   *
   * Description: 
   *       Decrease brightnesss.
   *
   * Parameters:
   *    IXML_Document * in -  action request document
   *    IXML_Document **out - action result document
   *    char **errorString - errorString (in case action was unsuccessful)
   *
   *****************************************************************************/
  int
  TvDeviceDecreaseBrightness( IN IXML_Document * in,
                              OUT IXML_Document ** out,
                              OUT char **errorString )
  {
      return IncrementBrightness( -1, in, out, errorString );
  }
  
  /******************************************************************************
   * TvDeviceCallbackEventHandler
   *
   * Description: 
   *       The callback handler registered with the SDK while registering
   *       root device.  Dispatches the request to the appropriate procedure
   *       based on the value of EventType. The four requests handled by the 
   *       device are: 
   *                   1) Event Subscription requests.  
   *                   2) Get Variable requests. 
   *                   3) Action requests.
   *
   * Parameters:
   *
   *   EventType -- The type of callback event
   *   Event -- Data structure containing event data
   *   Cookie -- Optional data specified during callback registration
   *
   *****************************************************************************/
  int
  TvDeviceCallbackEventHandler( Upnp_EventType EventType,
                                void *Event,
                                void *Cookie )
  {
  
      switch ( EventType ) {
  
          case UPNP_EVENT_SUBSCRIPTION_REQUEST:
  
              TvDeviceHandleSubscriptionRequest( ( struct
                                                   Upnp_Subscription_Request
                                                   * )Event );
              break;
  
          case UPNP_CONTROL_GET_VAR_REQUEST:
              TvDeviceHandleGetVarRequest( ( struct Upnp_State_Var_Request
                                             * )Event );
              break;
  
          case UPNP_CONTROL_ACTION_REQUEST:
              TvDeviceHandleActionRequest( ( struct Upnp_Action_Request * )
                                           Event );
              break;
  
              /*
                 ignore these cases, since this is not a control point 
               */
          case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
          case UPNP_DISCOVERY_SEARCH_RESULT:
          case UPNP_DISCOVERY_SEARCH_TIMEOUT:
          case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
          case UPNP_CONTROL_ACTION_COMPLETE:
          case UPNP_CONTROL_GET_VAR_COMPLETE:
          case UPNP_EVENT_RECEIVED:
          case UPNP_EVENT_RENEWAL_COMPLETE:
          case UPNP_EVENT_SUBSCRIBE_COMPLETE:
          case UPNP_EVENT_UNSUBSCRIBE_COMPLETE:
              break;
  
          default:
              SampleUtil_Print
                  ( "Error in TvDeviceCallbackEventHandler: unknown event type %d\n",
                    EventType );
      }
  
      /*
         Print a summary of the event received 
       */
      SampleUtil_PrintEvent( EventType, Event );
  
      return ( 0 );
  }
  
  /******************************************************************************
   * TvDeviceStop
   *
   * Description: 
   *       Stops the device. Uninitializes the sdk. 
   *
   * Parameters:
   *
   *****************************************************************************/
  int
  TvDeviceStop()
  {
      UpnpUnRegisterRootDevice( device_handle );
      UpnpFinish();
      SampleUtil_Finish();
      ithread_mutex_destroy( &TVDevMutex );
      return UPNP_E_SUCCESS;
  }
  
  /******************************************************************************
   * TvDeviceStart
   *
   * Description: 
   *      Initializes the UPnP Sdk, registers the device, and sends out 
   *      advertisements.  
   *
   * Parameters:
   *
   *   ip_address - ip address to initialize the sdk (may be NULL)
   *                if null, then the first non null loopback address is used.
   *   port       - port number to initialize the sdk (may be 0)
   *                if zero, then a random number is used.
   *   desc_doc_name - name of description document.
   *                   may be NULL. Default is tvcombodesc.xml
   *   web_dir_path  - path of web directory.
   *                   may be NULL. Default is ./web (for Linux) or ../tvdevice/web
   *                   for windows.
   *   pfun          - print function to use.  
   *
   *****************************************************************************/
  int
  TvDeviceStart( char *ip_address,
                 unsigned short port,
                 char *desc_doc_name,
                 char *web_dir_path,
                 print_string pfun )
  {
      int ret = UPNP_E_SUCCESS;
  
      char desc_doc_url[DESC_URL_SIZE];
  
      ithread_mutex_init( &TVDevMutex, NULL );
  
      SampleUtil_Initialize( pfun );
  
      SampleUtil_Print(
          "Initializing UPnP Sdk with\n"
          "\tipaddress = %s port = %u\n",
          ip_address, port );
  
      if( ( ret = UpnpInit( ip_address, port ) ) != UPNP_E_SUCCESS ) {
          SampleUtil_Print( "Error with UpnpInit -- %d\n", ret );
          UpnpFinish();
          return ret;
      }
  
      if( ip_address == NULL ) {
          ip_address = UpnpGetServerIpAddress();
      }
  
      if( port == 0 ) {
          port = UpnpGetServerPort();
      }
  
      SampleUtil_Print(
          "UPnP Initialized\n"
  	"\tipaddress= %s port = %u\n",
          ip_address, port );
  
      if( desc_doc_name == NULL ) {
          desc_doc_name = "tvcombodesc.xml";
      }
  
      if( web_dir_path == NULL ) {
          web_dir_path = DEFAULT_WEB_DIR;
      }
  
      snprintf( desc_doc_url, DESC_URL_SIZE, "http://%s:%d/%s", ip_address,
                port, desc_doc_name );
  
      SampleUtil_Print( "Specifying the webserver root directory -- %s\n",
                        web_dir_path );
      if( ( ret =
            UpnpSetWebServerRootDir( web_dir_path ) ) != UPNP_E_SUCCESS ) {
          SampleUtil_Print
              ( "Error specifying webserver root directory -- %s: %d\n",
                web_dir_path, ret );
          UpnpFinish();
          return ret;
      }
  
      SampleUtil_Print(
          "Registering the RootDevice\n"
          "\t with desc_doc_url: %s\n",
          desc_doc_url );
  
      if( ( ret = UpnpRegisterRootDevice( desc_doc_url,
                                          TvDeviceCallbackEventHandler,
                                          &device_handle, &device_handle ) )
          != UPNP_E_SUCCESS ) {
          SampleUtil_Print( "Error registering the rootdevice : %d\n", ret );
          UpnpFinish();
          return ret;
      } else {
          SampleUtil_Print(
              "RootDevice Registered\n"
              "Initializing State Table\n");
          TvDeviceStateTableInit( desc_doc_url );
          SampleUtil_Print("State Table Initialized\n");
  
          if( ( ret =
                UpnpSendAdvertisement( device_handle, default_advr_expire ) )
              != UPNP_E_SUCCESS ) {
              SampleUtil_Print( "Error sending advertisements : %d\n", ret );
              UpnpFinish();
              return ret;
          }
  
          SampleUtil_Print("Advertisements Sent\n");
      }
      return UPNP_E_SUCCESS;
  }
  
  