MythTV UDP Relay

From MythTV Official Wiki
Jump to: navigation, search

Time.png Outdated: This utility uses the old mythtvosd protocol, and is not compatible with MythMessage used in 0.25 and later.


Author Ken Bass
Description Transforms and relays an XML message over UDP. For use with Caller ID Broadcaster.
Supports



Important.png Note: For compilation, libxml2, and libxslt are required. Both were available on my default redhat 9 installation.

This program receives an XML structure over UDP. It then applies an XSLT (XSL transformation). The resultant XML structure is broadcast over UDP using a different port.

As an example, suppose you want to support caller ID. An OSD theme design needs to have a container/textarea allocated to put your caller ID info.

In the osd.xml file there should be something like this to contain the caller ID information:

(portion of osd.xml)

  <font name="notifyfont">
    <color>255</color>
    <size>12</size>
    <outline>no</outline>
  </font>

  <container name="notify_cid_info" priority="10" fademovement="0,6">
    <image name="background">
      <filename>osd-callerid.png</filename>
      <position>0,200</position>
    </image>
    <textarea name="notify_cid_name">
      <area>410,210,200,30</area>
      <font>notifyfont</font>
      <multiline>no</multiline>
    </textarea>
    <textarea name="notify_cid_num">
      <area>410,240,200,30</area>
      <font>notifyfont</font>
      <multiline>no</multiline>
    </textarea>
  </container>

The RESULT of the XSLT transformation should like something like:

<mythnotify version="1">
  <container name="notify_cid_info">
    <textarea name="notify_cid_name">
      <value>NAME: %caller_name%</value>
    </textarea>
    <textarea name="notify_cid_num">
      <value>NUM : %caller_num%</value>
    </textarea>
  </container>
</mythnotify>

Be sure the container names / textarea names correspond to the osd.xml

The included callerid.xsl XSLT file transforms input XML below into the format needed by mythtvnotify:

<telephone-event type="callerID" TTY="ttyS0">
  <time stamp="1063853301" zone="EDT" daylight-savings="1" second="21" minute="4
8" hour="22" day="17" month="9" year="2003">Wed Sep 17 22:48:21 2003</time>
  <ring-type>1</ring-type>
  <from>
    <name>JOE SCHMOE</name>
    <number>3015551212</number>
  </from>
</telephone-event>

Of course the callerid.xsl could be modified to extract whatever fields you are interested in and display them on the OSD.

The provided XSLT works in conjunction with the cidbcast program. The cidbcast program generates an XML 'event' in the above format.


List-add.png Todo: This program process one type of event. With a little bit of work, a configuration file should be added that specifies 'input udp port', 'output udp port' and the 'xslt file'. This would allow multiple events to be received and transformed to mythnotify events.

Example:

./mythudprelay --xslfile=callerid.xsl --bcast="255.255.255.255" --udpport_in=6947 --udpport_out=6948



Script.png mythudprelay.c

/* Ken Bass ( kbass a kenbass d com ) 09/13/2003
**
** MythTV UDP Relay
**
*/

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <sys/timeb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>
#include <termios.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xmlmemory.h>
#include <libxml/debugXML.h>
#include <libxml/HTMLtree.h>
#include <libxml/xmlIO.h>
#include <libxml/DOCBparser.h>
#include <libxml/xinclude.h>
#include <libxml/catalog.h>
#include <libxslt/xslt.h>
#include <libxslt/xsltInternals.h>
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>

#define UDP_RCV_BUFSIZE (64*1024)

int verbose = 0;
char xml_filename[255];

typedef struct 
{
  int fd;
  int port;
  struct sockaddr_in addr;
  char bcast_addr[16];
  int bcast_addr_hex;
} SOCKET_HANDLE;

typedef struct 
{

  SOCKET_HANDLE rcv_sock_handle;
  SOCKET_HANDLE snd_sock_handle;
  char udp_rcv_buffer[UDP_RCV_BUFSIZE];
  char xml_output_buffer[UDP_RCV_BUFSIZE];
  char xslt_filename[255];
} GLOBALS;

int ipaddr_to_int(const char *stringIP, unsigned int *ip)
{
  int a,b,c,d;
  
  if (4 != sscanf(stringIP, "%d.%d.%d.%d", &a, &b, &c, &d))
    {
      return -1;
    }
  
  *ip = ((a&0xFF) << 24) +
    ((b&0xFF) << 16) +
    ((c&0xFF) <<  8) +
    (d&0xFF);
  
  return 0;
}

int setup_rcv_socket(SOCKET_HANDLE* sock, int port)
{
  int yes = 1;
  sock->port = port;
  
  /* create what looks like an ordinary UDP socket */
  if ( (sock->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0 )
    {
      perror("socket");
      return -1;
    }
  
  /* set up destination address */
  sock->addr.sin_family = AF_INET;
  sock->addr.sin_addr.s_addr = htonl(INADDR_ANY);
  sock->addr.sin_port = htons(port);
  
  /* set up for reusable broadcast port */
  if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int) ) < 0)
    {
      perror("setsockopt");
      return -2;
    }
  
  /* bind to receive address */
  if ( bind(sock->fd, (struct sockaddr*) &(sock->addr), sizeof(sock->addr)) < 0 )
    {
      perror("bind");
      return -3;
    }
  
  return 0;
}

int setup_snd_socket(SOCKET_HANDLE* sock, int port, char *addr)
{
  int yes = 1;

  strcpy(sock->bcast_addr, addr);
  if (ipaddr_to_int(sock->bcast_addr, &sock->bcast_addr_hex) < 0)
  {
    printf("Error in Broadcast address %s\n",
	   sock->bcast_addr);
    return -1;
  }

  sock->port = port;

  /* create what looks like an ordinary UDP socket */
  if ( (sock->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0 )
    {
      perror("socket");
      return -1;
    }
  
  /* set up destination address */
  sock->addr.sin_family = AF_INET;
  sock->addr.sin_addr.s_addr = htonl(sock->bcast_addr_hex);
  sock->addr.sin_port = htons(sock->port);
  
  /* set up for reusable broadcast port */
  if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int) ) < 0)
    {
      perror("setsockopt");
      return -2;
    }

  /* setsockopt */
  if (setsockopt(sock->fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(int) ) < 0) 
    {
      perror("so_broadcast");
      return -3;
    }
 
  return 0;
}

int send_xml_output(GLOBALS *globals, SOCKET_HANDLE* sock)
{
  int rc;

  /* now just sendto() our destination! */
  rc = sendto(sock->fd,
	      globals->xml_output_buffer,
	      strlen(globals->xml_output_buffer),0,(struct sockaddr *) &sock->addr,
	      sizeof(sock->addr));
  if (rc < 0)
    {
      perror("sendto");
      return -1;
    }
  else
    {
      if (verbose)
      {
	printf("Sent UDP/XML packet to IP %s port %d (%d bytes)\n", 
	       sock->bcast_addr, sock->port, rc);
      }
    }

  return 0;
  
} 

/* Transform received XML data using XSL */
int process_udp_xml(GLOBALS *globals)
{
  xsltStylesheetPtr cur = NULL;
  xmlDocPtr doc, res;
  xmlChar *xml_output;
  int xml_output_len;
  int rc;

  xmlSubstituteEntitiesDefault(1);
  xmlLoadExtDtdDefaultValue = 1;
  cur = xsltParseStylesheetFile((const xmlChar *)globals->xslt_filename);
  if (cur == NULL)
    {
      xsltCleanupGlobals();
      xmlCleanupParser();
      return -1;
    }

  doc = xmlParseMemory(globals->udp_rcv_buffer, UDP_RCV_BUFSIZE);
  if (doc == NULL)
    {
      xsltFreeStylesheet(cur);
      xsltCleanupGlobals();
      xmlCleanupParser();
      return -1;
    }
  res = xsltApplyStylesheet(cur, doc, NULL);
  if (res == NULL)
    {
      xmlFreeDoc(res);
      xsltFreeStylesheet(cur);
      xsltCleanupGlobals();
      xmlCleanupParser();
      return -1;
    }

  rc = xsltSaveResultToString(&xml_output,
				&xml_output_len,
				res,
				cur);

  /* If transformation is empty - error. Its possible the incoming
  ** XML was not an event the XSLT handles */
  if ((rc < 0) || (xml_output_len == 0))
  {
    
    xsltFreeStylesheet(cur);
    xmlFreeDoc(res);
    xmlFreeDoc(doc);
    
    xsltCleanupGlobals();
    xmlCleanupParser();
    
    return -1;
  }

  strncpy(globals->xml_output_buffer, xml_output, UDP_RCV_BUFSIZE-1);
  
  xmlFree(xml_output);

  xsltFreeStylesheet(cur);
  xmlFreeDoc(res);
  xmlFreeDoc(doc);
  
  xsltCleanupGlobals();
  xmlCleanupParser();

  return 0;
}

int waitfor_udp(GLOBALS *globals, SOCKET_HANDLE* sock)
{
  int  nbytes;       /* Number of bytes read */
  fd_set udp_rd_set;
  int rv;

  FD_ZERO(&udp_rd_set);
  FD_SET (sock->fd, &udp_rd_set);

  memset (globals->udp_rcv_buffer, 0, UDP_RCV_BUFSIZE);

  rv = select(sock->fd+1, &udp_rd_set, NULL, NULL, NULL);
  if (rv < 0)
  {
    printf("Select failed\n");
    return -1;
  }

  if (FD_ISSET(sock->fd, &udp_rd_set))
    {
      nbytes = read(sock->fd, globals->udp_rcv_buffer, UDP_RCV_BUFSIZE);
      if (nbytes <= 0)
      {
	return -1;
      }
      else
      {
	if (verbose)
	{
	  printf("got UDP data (%d bytes)\n", nbytes);
	}
      }
    }

  return 0;
}

void print_help(char *progname)
{
  printf("\nUsage: %s [OPTION]\n", progname);
  printf("A caller id UDP broadcast utility for MythTV notify.\n\n");
  printf("  -i, --udpport_in  : UDP port to monitor (--udpport_in=6947)\n");
  printf("  -o, --udpport_out : UDP port to monitor (--udpport_out=6948)\n");
  printf("  -b, --bcast     : UDP broadcast address (--bcast=255.255.255.255)\n");
  printf("  -x, --xslfile   : XSL file to use as a template (--xslfile=cid.xml)\n");
  printf("  -v, --verbose   : some verbose debug stuff\n");
  printf("\nAn XSL (XSLT) file is required - it is used to transform the incoming\n");
  printf("XML to the output XML (mythnotify)\n");
}

int main(int argc, char *argv[])
{
  int file_arg_found = 0;
  static GLOBALS globals;
  int done = 0;
  int option_index = 0, c;
  int rcv_udp_port;
  int snd_udp_port;
  char bcast_addr[16];
  int bcast_addr_hex;

  static struct option long_options[] = 
  {
    {"udpport_in", optional_argument, 0, 'i'},
    {"udpport_out", optional_argument, 0, 'o'},
    {"bcast", optional_argument, 0, 'b'},
    {"xslfile", required_argument, 0, 'x'},
    {"help", no_argument, 0, 'h'},
    {"verbose", no_argument, 0, 'v'},
    {0, 0, 0, 0}
  };

  /* Defaults */
  strcpy(bcast_addr, "255.255.255.255");
  ipaddr_to_int(bcast_addr, &bcast_addr_hex);
  rcv_udp_port = 6947;
  snd_udp_port = 6948;
  strcpy(globals.xslt_filename, "");

  while (1) 
  {
    c = getopt_long (argc, argv, "i:o:b:x:hv",
		     long_options, &option_index);
    if (c == -1)
      break;
    
    switch (c) 
    {
      
      case 'i':
	rcv_udp_port = atoi(optarg);
	break;

      case 'o':
	snd_udp_port = atoi(optarg);
	break;

      case 'b':
	strncpy(bcast_addr, optarg, sizeof(bcast_addr));
	if (ipaddr_to_int(bcast_addr, &bcast_addr_hex) < 0)
	{
	  printf("Error in Broadcast address %s\n",
		 bcast_addr);
	  exit(1);
	}
	break;

      case 'x':
	strncpy(globals.xslt_filename, optarg, sizeof(globals.xslt_filename)-1);
	file_arg_found = 1;
	break;

      case 'h':
	print_help(argv[0]);
	exit(0);
	break;	

      case 'v':
	verbose = 1;
	printf("Verbose mode enabled\n");
	break;

      default:
	print_help(argv[0]);
	exit(0);
    }
  }

  if (!file_arg_found)
  {
    printf("No xslfile provided; --xslfile=file.xsl arg required\n");
    return -1;
  }

  if (rcv_udp_port == snd_udp_port)
    {
      printf("UDP input and output ports cannot be the same\n");
      return -1;
    }

  if (setup_rcv_socket(&globals.rcv_sock_handle, rcv_udp_port) < 0)
  {
    printf("error in setup_rcv_sockets\n");
    return -1;
  }

  if (setup_snd_socket(&globals.snd_sock_handle, 
		       snd_udp_port, bcast_addr) < 0)
  {
    printf("error in setup_snd_sockets\n");
    return -1;
  }

  done = 0;
  while (!done)
  {
    if (waitfor_udp(&globals, &globals.rcv_sock_handle) >= 0)
      {

	if (process_udp_xml(&globals) >= 0)
	{

	  if (verbose)
	  {
	    printf("%s", globals.xml_output_buffer);
	  }
	  
	  send_xml_output(&globals, &globals.snd_sock_handle);
	}
      }
  }

  return 0;
}


Script.png callerid.xsl

<?xml version="1.0" encoding="ISO-8859-1"?>

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="/">
    <xsl:apply-templates select="telephone-event[@type='callerID']" />
  </xsl:template>
  
  <xsl:template match="telephone-event"> 
  <mythnotify version="1">
    <container name="notify_cid_info">
      <textarea name="notify_cid_line">
        <value>LINE #<xsl:value-of select="ring-type"/></value>
      </textarea>
      <textarea name="notify_cid_name">
        <value>NAME: <xsl:value-of select="from/name"/></value>
      </textarea>
      <textarea name="notify_cid_num">
        <value>NUM : <xsl:variable name="phone" select="from/number/text()"/>
        <value>NUM : <xsl:variable name="phone" select="from/number/text()"/>
<xsl:choose><xsl:when test="(number($phone) = $phone) and (string-length($phone) = 7)"><xsl:value-of select="concat(substring($phone,1,3),'-',substring($phone,4,4))"/></xsl:when><xsl:when test="number($phone) = $phone"><xsl:value-of select="concat('(',substring($phone,1,3),') ',substring($phone,4,3),'-',substring($phone,7,4))"/></xsl:when><xsl:otherwise><xsl:value-of select="$phone"/></xsl:otherwise></xsl:choose>
</value>
      </textarea>
      <textarea name="notify_cid_dt">
        <value>DATE: <xsl:value-of select="time/@month"/>/<xsl:value-of select="time/@day"/> TIME: <xsl:value-of select="time/@hour"/>:<xsl:value-of select="time/@minute"/> </value>
      </textarea>
    </container>
  </mythnotify>
</xsl:template>

</xsl:stylesheet>


Script.png Makefile

#################################################################################
#  
#  makefile
#
#################################################################################

CC       =  gcc
LINK     =  gcc
RM       =  rm 
RM_FLAGS =  -f

CFLAGS  	= -g -Wall `xml2-config --cflags` -O
CDEFS   	= -DDEBUG

# Libraries

STDLIBS    = -lm `xml2-config --libs` -lxslt
X11LIBS    =  -L/usr/X11R6/lib -lX11
PROGRAM = mythudprelay

#################################################################################

.SUFFIXES: .o .c .flex
.c.o:   ;       $(CC)  $(CFLAGS) $(CDEFS) -c $< -o $*.o

mythudprelay: mythudprelay.o
	$(LINK) -o mythudprelay mythudprelay.o $(STDLIBS)

#################################################################################

clean:
	$(RM) $(RM_FLAGS) $(PROGRAM) *.o *~ core

#################################################################################