Sa control.cc

From MythTV Official Wiki
Jump to: navigation, search

Important.png Note: The correct title of this article is sa_control.cc. It appears incorrectly here due to technical restrictions.


Author Martin Purschke
Description Channel-change script for use with the Scientific Atlanta 4250HDC cable box.
Supports


The Scientific Atlanta 4250HDC cable box is reported as being unsupported by the sa3250ch app. It also produces a warning with mythbackend's internal channel changer. The internal changer succeeds in changing the channel after all.

I took the skeleton of sa3250ch.c and the innards of firewiredevice.h/cpp and linuxavcinfo.h/cpp, and made this utility. It allows to change the channel, query the power status, and turn the unit on or off. I need it mainly because I switched to the Hauppauge HD-PVR.

From the help:

   Usage: sa_control [-v] [-q] [ channel | on | off ]

        -h : this help -v : verbose, multiple -v for more verbosity -q : query power status, also sets exit status

            <channel> : set channel, implies power on on : switch on off : switch off

   Examples: sa_control -q sa_control 702 sa_control off

Since the code comes from the internal changer, which does not take special action based on the SA model number (as far as I can tell), this should work for other models as well. I can test only my box, obviously.

Along the same lines, the "SA4250HDC" warning in firewiredevice.cpp around line 184 can be removed, as the commands succeed. I think this is too minor for a patch.

Update:

I added a new feature for a partial solution for cable boxes which take a long time to tune. There is a new -w <ms> switch that adds an artificial delay after the channel switch. This allows to adjust the channel change command to wait with the onset of the recording until the change has taken effect. One can cut out the usual first few seconds of the previous program in the recording.

sa_control -w 5000 702

will switch to channel 702 and theen wait another 5 seconds before exiting. The help file says now

# sa_control -h
Usage: sa_control [-v] [-q] [-w <ms> [ <channel> | on | off ]
       -h         : this help
       -v         : verbose, multiple -v for more verbosity
       -q         : query power status, also sets exit status
        <channel> : set channel, implies power on
       -w <ms>    : after a channel change, wait so many
                    more milliseconds to allow the box time to tune
        on        : switch on
        off       : switch off
Examples:
 sa_control -q
 sa_control 702
 sa_control -w 6000 702
 sa_control off

I have also cleaned up some of the comments in the code. I attach the whole new source file instead of a patch.


Script.png sa_control.cc

/*
 * sa_control - control for Scientific Atlanta Cable Boxes, including
 * the 4250 HDC which is unsupported in sa3250ch.c  
 * Based on sa3250ch.c, firewiredevice.{h.cpp}, and linuxavcinfo.{h,cpp}
 * 
 * Martin Purschke ( mpurschke (at) gmail.com ) 11/28/2009
 *
 * allows to change the channel, query the power status, and turn
 * the power on and off
 *
 * Usage: sa_control [-v] [-q] [ channel | on | off ]
 *      -h         : this help
 *      -v         : verbose, multiple -v for more verbosity
 *      -q         : query power status, also sets exit status
 *       <channel> : set channel, implies power on
 *      -w <ms>    : after a channel change, wait so many
 *                   more milliseconds to allow the box time to tune
 *       on        : switch on
 *       off       : switch off
 * Examples:
 * sa_control -q
 * sa_control -q -v
 * sa_control -q -v -v
 * sa_control 702
 * sa_control -w 6000 702
 * sa_control off
 *
 * Build it with 
 * g++ -o sa_control sa_control.cc  -lrom1394 -lavc1394 -lraw1394
 *
 * Revision:
 * version 1.1 added thge -w switch for a delay
 * version 1.2 replaced the single occurrence of uint8_t with __uint8_t
 *             which is more portable.
 * version 1.3 had replaced uint8_t but overlooked uint_32
 *             on ubuntu the stdio.h is not pulled in
 *              
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <libavc1394/rom1394.h>
#include <libavc1394/avc1394.h>
#include <libraw1394/raw1394.h>
#include <sys/types.h>
#include <getopt.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <iostream>
#include <iomanip>
#include <vector>

using namespace std;

#define COMMAND_CHANNEL 1
#define COMMAND_POWER   2
#define COMMAND_QUERY   3

typedef enum
  {
    kAVCPowerOn,
    kAVCPowerOff,
    kAVCPowerUnknown,
    kAVCPowerQueryFailed,
  } PowerState;

// AVC commands
typedef enum
  {
    kAVCControlCommand         = 0x00,
    kAVCStatusInquiryCommand   = 0x01,
    kAVCSpecificInquiryCommand = 0x02,
    kAVCNotifyCommand          = 0x03,
    kAVCGeneralInquiryCommand  = 0x04,

    kAVCNotImplementedStatus   = 0x08,
    kAVCAcceptedStatus         = 0x09,
    kAVCRejectedStatus         = 0x0a,
    kAVCInTransitionStatus     = 0x0b,
    kAVCImplementedStatus      = 0x0c,
    kAVCChangedStatus          = 0x0d,

    kAVCInterimStatus          = 0x0f,
    kAVCResponseImplemented    = 0x0c,
  } IEEE1394Command;

// AVC unit addresses
typedef enum
  {
    kAVCSubunitId0                = 0x00,
    kAVCSubunitId1                = 0x01,
    kAVCSubunitId2                = 0x02,
    kAVCSubunitId3                = 0x03,
    kAVCSubunitId4                = 0x04,
    kAVCSubunitIdExtended         = 0x05,
    kAVCSubunitIdIgnore           = 0x07,

    kAVCSubunitTypeVideoMonitor   = (0x00 << 3),
    kAVCSubunitTypeAudio          = (0x01 << 3),
    kAVCSubunitTypePrinter        = (0x02 << 3),
    kAVCSubunitTypeDiscRecorder   = (0x03 << 3),
    kAVCSubunitTypeTapeRecorder   = (0x04 << 3),
    kAVCSubunitTypeTuner          = (0x05 << 3),
    kAVCSubunitTypeCA             = (0x06 << 3),
    kAVCSubunitTypeVideoCamera    = (0x07 << 3),
    kAVCSubunitTypePanel          = (0x09 << 3),
    kAVCSubunitTypeBulletinBoard  = (0x0a << 3),
    kAVCSubunitTypeCameraStorage  = (0x0b << 3),
    kAVCSubunitTypeMusic          = (0x0c << 3),
    kAVCSubunitTypeVendorUnique   = (0x1c << 3),
    kAVCSubunitTypeExtended       = (0x1e << 3),
    kAVCSubunitTypeUnit           = (0x1f << 3),
  } IEEE1394UnitAddress;

// AVC opcode
typedef enum
  {
    // Unit
    kAVCUnitPlugInfoOpcode               = 0x02,
    kAVCUnitDigitalOutputOpcode          = 0x10,
    kAVCUnitDigitalInputOpcode           = 0x11,
    kAVCUnitChannelUsageOpcode           = 0x12,
    kAVCUnitOutputPlugSignalFormatOpcode = 0x18,
    kAVCUnitInputPlugSignalFormatOpcode  = 0x19,
    kAVCUnitConnectAVOpcode              = 0x20,
    kAVCUnitDisconnectAVOpcode           = 0x21,
    kAVCUnitConnectionsOpcode            = 0x22,
    kAVCUnitConnectOpcode                = 0x24,
    kAVCUnitDisconnectOpcode             = 0x25,
    kAVCUnitUnitInfoOpcode               = 0x30,
    kAVCUnitSubunitInfoOpcode            = 0x31,
    kAVCUnitSignalSourceOpcode           = 0x1a,
    kAVCUnitPowerOpcode                  = 0xb2,

    // Common Unit + Subunit
    kAVCCommonOpenDescriptorOpcode       = 0x08,
    kAVCCommonReadDescriptorOpcode       = 0x09,
    kAVCCommonWriteDescriptorOpcode      = 0x0A,
    kAVCCommonSearchDescriptorOpcode     = 0x0B,
    kAVCCommonObjectNumberSelectOpcode   = 0x0D,
    kAVCCommonPowerOpcode                = 0xB2,
    kAVCCommonReserveOpcode              = 0x01,
    kAVCCommonPlugInfoOpcode             = 0x02,
    kAVCCommonVendorDependentOpcode      = 0x00,

    // Panel
    kAVCPanelPassThrough                 = 0x7c,
  } IEEE1394Opcode;

// AVC param 0
typedef enum
  {
    kAVCPowerStateOn           = 0x70,
    kAVCPowerStateOff          = 0x60,
    kAVCPowerStateQuery        = 0x7f,
  } IEEE1394UnitPowerParam0;

typedef enum
  {
    kAVCPanelKeySelect          = 0x00,
    kAVCPanelKeyUp              = 0x01,
    kAVCPanelKeyDown            = 0x02,
    kAVCPanelKeyLeft            = 0x03,
    kAVCPanelKeyRight           = 0x04,
    kAVCPanelKeyRightUp         = 0x05,
    kAVCPanelKeyRightDown       = 0x06,
    kAVCPanelKeyLeftUp          = 0x07,
    kAVCPanelKeyLeftDown        = 0x08,
    kAVCPanelKeyRootMenu        = 0x09,
    kAVCPanelKeySetupMenu       = 0x0A,
    kAVCPanelKeyContentsMenu    = 0x0B,
    kAVCPanelKeyFavoriteMenu    = 0x0C,
    kAVCPanelKeyExit            = 0x0D,

    kAVCPanelKey0               = 0x20,
    kAVCPanelKey1               = 0x21,
    kAVCPanelKey2               = 0x22,
    kAVCPanelKey3               = 0x23,
    kAVCPanelKey4               = 0x24,
    kAVCPanelKey5               = 0x25,
    kAVCPanelKey6               = 0x26,
    kAVCPanelKey7               = 0x27,
    kAVCPanelKey8               = 0x28,
    kAVCPanelKey9               = 0x29,
    kAVCPanelKeyDot             = 0x2A,
    kAVCPanelKeyEnter           = 0x2B,
    kAVCPanelKeyClear           = 0x2C,

    kAVCPanelKeyChannelUp       = 0x30,
    kAVCPanelKeyChannelDown     = 0x31,
    kAVCPanelKeyPreviousChannel = 0x32,
    kAVCPanelKeySoundSelect     = 0x33,
    kAVCPanelKeyInputSelect     = 0x34,
    kAVCPanelKeyDisplayInfo     = 0x35,
    kAVCPanelKeyHelp            = 0x36,
    kAVCPanelKeyPageUp          = 0x37,
    kAVCPanelKeyPageDown        = 0x38,

    kAVCPanelKeyPower           = 0x40,
    kAVCPanelKeyVolumeUp        = 0x41,
    kAVCPanelKeyVolumeDown      = 0x42,
    kAVCPanelKeyMute            = 0x43,
    kAVCPanelKeyPlay            = 0x44,
    kAVCPanelKeyStop            = 0x45,
    kAVCPanelKeyPause           = 0x46,
    kAVCPanelKeyRecord          = 0x47,
    kAVCPanelKeyRewind          = 0x48,
    kAVCPanelKeyFastForward     = 0x49,
    kAVCPanelKeyEject           = 0x4a,
    kAVCPanelKeyForward         = 0x4b,
    kAVCPanelKeyBackward        = 0x4c,

    kAVCPanelKeyAngle           = 0x50,
    kAVCPanelKeySubPicture      = 0x51,

    kAVCPanelKeyTuneFunction    = 0x67,

    kAVCPanelKeyPress           = 0x00,
    kAVCPanelKeyRelease         = 0x80,

  } IEEE1394PanelPassThroughParam0;





/* SA3250HD IDs */
/* WARNING: Please update firewiredevice.cpp when adding to this list. */

#define SA_VENDOR_ID1           0x00000a73
#define SA_VENDOR_ID2           0x00000f21
#define SA_VENDOR_ID3           0x000011e6
#define SA_VENDOR_ID4           0x000014f8
#define SA_VENDOR_ID5           0x00001692
#define SA_VENDOR_ID6           0x00001868
#define SA_VENDOR_ID7           0x00001947
#define SA_VENDOR_ID8           0x00001ac3
#define SA_VENDOR_ID9           0x00001bd7
#define SA_VENDOR_ID10          0x00001cea
#define SA_VENDOR_ID11          0x00001e6b
#define SA_VENDOR_ID12          0x000021be
#define SA_VENDOR_ID13          0x0000223a
#define SA_VENDOR_ID14          0x000022ce
#define SA_VENDOR_ID15          0x000023be
#define SA_VENDOR_ID16          0x0000252e

#define SA3250HD_MODEL_ID1      0x00000be0
#define SA4200HD_MODEL_ID1      0x00001072
#define SA4250HDC_MODEL_ID1     0x000010cc
#define SA8300HD_MODEL_ID1      0x000022ce


#define STARTING_NODE 0

int verbose = 0;

void exitmsg()
{
  cout << "Usage: sa_control [-v] [-q] [-w <ms>] [ <channel> | on | off ]" << endl;
  cout << "       sa_control -h for help" << endl;
  exit(1);
}

void exithelp()
{
  cout << "Usage: sa_control [-v] [-q] [-w <ms> [ <channel> | on | off ]" << endl;
  cout << "       -h         : this help" << endl;
  cout << "       -v         : verbose, multiple -v for more verbosity" << endl;
  cout << "       -q         : query power status, also sets exit status" << endl;
  cout << "        <channel> : set channel, implies power on" << endl;
  cout << "       -w <ms>    : after a channel change, wait so many " << endl;
  cout << "                    more milliseconds to allow the box time to tune" << endl;
  cout << "        on        : switch on" << endl;
  cout << "        off       : switch off" << endl;
  cout << "Examples:" << endl;
  cout << " sa_control -q" << endl;
  cout << " sa_control 702" << endl;
  cout << " sa_control -w 6000 702" << endl;
  cout << " sa_control off" << endl;
  exit(0);
}


// adapted from linuxavcinfo.cpp

int SendAVCCommand(const vector<__uint8_t>  &_cmd,
		   vector<__uint8_t>        &result,
		   int                     retry_cnt,
		   raw1394handle_t &fw_handle
		   )
{
  retry_cnt = (retry_cnt < 0) ? 2 : retry_cnt;
  
  result.clear();
  
  int node =1;
  
  vector<__uint8_t> cmd = _cmd;
  while (cmd.size() & 0x3)
    {
      cmd.push_back(0x00);
    }
  
  if (cmd.size() > 4096)
    return 1;
  
  __uint32_t cmdbuf[1024];
  if (verbose >2)
    {
      for (unsigned int i = 0; i < cmd.size(); i++)
	{ 
	  std::cout << "avc command " << setw(3)<< i << "  0x" << hex << (unsigned int) cmd[i] << dec << std::endl;
	}
    }
  
  for (unsigned int i = 0; i < cmd.size(); i+=4)
    {
      cmdbuf[i>>2] = cmd[i]<<24 | cmd[i+1]<<16 | cmd[i+2]<<8 | cmd[i+3];
    }
  unsigned int result_length = 0;
  
  
  __uint32_t *ret = avc1394_transaction_block(fw_handle, node, cmdbuf, cmd.size() >> 2, retry_cnt);
  result_length = cmd.size() >> 2;
  
  if (!ret)
    return 1;
  
  for (unsigned int i = 0; i < result_length; i++)
    {
      result.push_back((ret[i]>>24) & 0xff);
      result.push_back((ret[i]>>16) & 0xff);
      result.push_back((ret[i]>>8)  & 0xff);
      result.push_back((ret[i])     & 0xff);
    }
  
  avc1394_transaction_block_close(fw_handle);
  
  return 0;
}




int SetPower (const int onoff, raw1394handle_t &handle)
{
  vector<__uint8_t> cmd;
  vector<__uint8_t> ret;
  
  cmd.push_back(kAVCControlCommand);
  cmd.push_back(kAVCSubunitTypeUnit | kAVCSubunitIdIgnore);
  cmd.push_back(kAVCUnitPowerOpcode);
  cmd.push_back((onoff) ? kAVCPowerStateOn : kAVCPowerStateOff);

  
  if (SendAVCCommand(cmd, ret, -1, handle))
    {
      std::cout <<  "Power command failed (no response)" << std::endl;
      return -1;
    }
  
  return 0;
}

int QueryPower(raw1394handle_t &handle)
{
    
  vector<__uint8_t> cmd;
  vector<__uint8_t> ret;
  
  cmd.push_back(kAVCStatusInquiryCommand);
  cmd.push_back(kAVCSubunitTypeUnit | kAVCSubunitIdIgnore);
  cmd.push_back(kAVCUnitPowerOpcode);
  cmd.push_back(kAVCPowerStateQuery);
  
  if (SendAVCCommand(cmd, ret, -1, handle))
    {
      return -1;
    }
  
  if (ret[0] != kAVCResponseImplemented)
    {
      return -2;
    }
  
  // check 1st operand..
  if (ret[3] == kAVCPowerStateOn)
    {
      return 1;
    }
  
    if (ret[3] == kAVCPowerStateOff)
      {
	return 0;
      }

    return -3;

}

int ChangeChannel(const int channelnumber, raw1394handle_t &handle)
{

  // power on if off
  if ( !QueryPower(handle) )
    {
      SetPower(1,handle);
      sleep(1);
    }

  vector<__uint8_t> cmd;
  vector<__uint8_t> ret;
  
  cmd.push_back(kAVCControlCommand);
  cmd.push_back(kAVCSubunitTypePanel);
  cmd.push_back(kAVCPanelPassThrough);
  cmd.push_back(kAVCPanelKeyTuneFunction | kAVCPanelKeyPress);  
  cmd.push_back(4); // operand length
  cmd.push_back((channelnumber>>8) & 0x0f);
  cmd.push_back(channelnumber & 0xff);
  cmd.push_back(0x00);
  cmd.push_back(0x00);
  
  if (SendAVCCommand(cmd, ret, -1, handle))
    {
      std::cout <<  "Channel change command failed (no response)" << std::endl;
      return -1;
    }
  
  return 0;
}


int main (int argc, char *argv[])
{
   rom1394_directory dir;
   int device = -1;
   int single = 0;
   int i;
   int channelnumber;
   int onoff;
   int sleeptime = 0;

   if (argc < 2) 
     exitmsg();

   extern char *optarg;
   extern int optind;

   int main_command = COMMAND_CHANNEL;  // assume our job is to change a channel

   char c;
   while ((c = getopt(argc, argv, "vqhw:")) != EOF)
     {
       switch (c)
	 {
	   
	 case 'h':
	   exithelp();
	   break;
	   
	 case 'w':
         if ( !sscanf(optarg, "%d", &sleeptime) ) exitmsg();
	 break;

	 case 'q':   // query
	   main_command =COMMAND_QUERY;  // job is to query the power state
	   break;

	 case 'v':   // verbose
	   verbose++;
	   break;
	 }
     }
   
   if ( main_command != COMMAND_QUERY )
     {
       if ( optind >= argc) exitmsg();
       if ( strcmp ( argv[optind], "on") ==0 )
	 {
	   onoff =1;
	   main_command =COMMAND_POWER;  // job is to power on or off
	 }
       else if (strcmp ( argv[optind], "off") ==0 )
	 {
	   onoff =0;
	   main_command =COMMAND_POWER;  // job is to power on or off
	 }
       else
	 {
	   if ( !sscanf(argv[optind], "%d", &channelnumber) ) exitmsg();
	   main_command =COMMAND_CHANNEL;  // job is to switch channels
	 } 
     }
   
   raw1394handle_t handle = raw1394_new_handle();
   
   if (!handle) 
     {
       if (!errno) 
	 {
	   fprintf(stderr, "Not Compatible!\n");
	 } 
       else 
	 {
	   perror("Couldn't get 1394 handle");
	   fprintf(stderr, "Is ieee1394, driver, and raw1394 loaded?\n");
	 }
       exit(1);
     } 
   
   
   if (raw1394_set_port(handle, 0) < 0) 
     {
       perror("couldn't set port");
       raw1394_destroy_handle(handle);
       exit(1);
     }
   
   int nc = raw1394_get_nodecount(handle);
   for (i=STARTING_NODE; i < nc; ++i) 
     {
       if (rom1394_get_directory(handle, i, &dir) < 0) 
	 {
	   fprintf(stderr,"error reading config rom directory for node %d\n", i);
	   raw1394_destroy_handle(handle);
	   exit(1);
	 }
       
       /* WARNING: Please update firewiredevice.cpp when adding to this list. */
       if (((dir.vendor_id == SA_VENDOR_ID1)  ||
	    (dir.vendor_id == SA_VENDOR_ID2)  ||
	    (dir.vendor_id == SA_VENDOR_ID3)  ||
	    (dir.vendor_id == SA_VENDOR_ID4)  ||
	    (dir.vendor_id == SA_VENDOR_ID5)  ||
	    (dir.vendor_id == SA_VENDOR_ID6)  ||
	    (dir.vendor_id == SA_VENDOR_ID7)  ||
	    (dir.vendor_id == SA_VENDOR_ID8)  ||
	    (dir.vendor_id == SA_VENDOR_ID9)  ||
	    (dir.vendor_id == SA_VENDOR_ID10) ||
	    (dir.vendor_id == SA_VENDOR_ID11) ||
	    (dir.vendor_id == SA_VENDOR_ID12) ||
	    (dir.vendor_id == SA_VENDOR_ID13) ||
	    (dir.vendor_id == SA_VENDOR_ID14) ||
	    (dir.vendor_id == SA_VENDOR_ID15) ||
	    (dir.vendor_id == SA_VENDOR_ID16)) &&
	   ((dir.model_id == SA3250HD_MODEL_ID1)  ||
	    (dir.model_id == SA4200HD_MODEL_ID1)  ||
	    (dir.model_id == SA4250HDC_MODEL_ID1) ||
	    (dir.model_id == SA8300HD_MODEL_ID1)))
	 {
	   if (verbose > 1)
	     {
	       cout << "Vendor id = 0x" << hex << dir.vendor_id << "  Model id = 0x" << dir.model_id << dec << endl;
	     }
	   
	   device = i;
	   break;
	 }
     }
   
   
   if (device == -1)
     {
       cout << "Could not find a cable box on bus" << endl;
       raw1394_destroy_handle(handle);
       return 1;
     }
   
   int returnvalue = 0;
   
   switch ( main_command)
     {
       
     case COMMAND_CHANNEL: // change channel
       if (verbose) 
	 {
	   cout << "Setting channel to " << channelnumber << endl;
	 }
       returnvalue = ChangeChannel(channelnumber, handle);
       // if so specified, sleep the inicated time
       if (sleeptime > 0) usleep ( sleeptime * 1000);
       break;
       
     case COMMAND_POWER: // change channel
       if (verbose) 
	 {
	   cout << "Turning power  " << ( (onoff) ? "on" : "off" ) << endl;
	 }
       returnvalue = SetPower(onoff, handle);
       break;
       
     case COMMAND_QUERY: // change channel
       int status = QueryPower(handle);
       if ( status < 0)
	 {
	   cout << "Error executing query" << endl;
	   exit(1);
	 }
       
       if (verbose) 
	 {
	   cout << "Power is " << ((status) ? "on" : "off")  << endl;
	 }
       else
	 {
	   cout  << ((status) ? "on" : "off")  << endl;
	 }
       returnvalue = (status)? 0 : 1;

     }

   raw1394_destroy_handle(handle);
   
   return returnvalue;
}