xPL Monkey - Home Automation - xPL and PHP



NOTICE

My involvement with xPL has come to an end. Automation has moved on considerably over the past few years, and it is now possible to buy a stand-alone controller off the shelf for a reasonable price, without having to spend hours writing your own code.

This website is being maintained as a record of my xPL development work up until 2011.

I have released the full source code of all my xPL projects into the public domain. You can download the archive from here.

Mal



  Server-Side xPL

The ability to send xPL messages from within web pages is an oft requested feature. An xPL ActiveX control does exist, but using this restricts the choice of operating system and web browser, and because it is a client-side solution, requires that the browser be running on a PC that is part of an xPL network.

For remote control, and to allow browsing by any client, a server-side solution is required. This article will demonstrate a method using PHP to send xPL messages.

Requirements:

1) A web server running on a PC that is part of an xPL network.
2) PHP with sockets enabled.
3) The sample scripts, unzipped and copied to the web server. Download

The scripts in this article were installed on a PC running Windows 2000 and IIS 5.0, with PHP version 5.4.2. They were tested with both Internet Explorer 7.0 and Firefox 2.0 under Windows Vista.

There are four files in the sample package:

xpl.php


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
  <title></title>
</head>

<body>
<?php
  error_reporting(E_ALL);
  ini_set('error_reporting', E_ALL);
    
  $broadcast = "255.255.255.255"; // Broadcast address
  $port = 3865;                   // xPL UDP assigned port
  $listenOnAddress="ANY_LOCAL";   // ListenOnAddress from xPL network settings.
//  $listenOnAddress="192.168.0.3";
  $xPLSource = "mal-php.intranet";  // Identifies the source of the message

  $xPLType = $_GET['type'];
  $xPLTarget = $_GET['target'];
  $xPLSchema = $_GET['schema'];
  $xPLBody = $_GET['body'];
	
  if( !function_exists( 'socket_create' ) )
  {
    trigger_error( 'Sockets not enabled in this version of PHP', E_USER_ERROR );
  }
	
  // create low level socket
  if( !$socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP ) )
  {
    trigger_error('Error creating new socket',E_USER_ERROR);		
  }

  // Set the socket to broadcast
  if( !socket_set_option( $socket, SOL_SOCKET, SO_BROADCAST, 1 ) )
  {
    trigger_error( 'Unable to set socket into broadcast mode', E_USER_ERROR );
  }
	
  // If the listenOnAddress is not set to ANY_LOCAL, we need to bind the socket.
  if( $listenOnAddress != "ANY_LOCAL" )
  {
    if( !socket_bind( $socket, $listenOnAddress, 0 ) )
    {
      trigger_error('Error binding socket to ListenOnAddress', E_USER_ERROR );
    }
  }

  // Send the message
  $msg = $xPLType."\n{\nhop=1\nsource=".$xPLSource."\ntarget="
                    .$xPLTarget."\n}\n".$xPLSchema."\n{\n".$xPLBody."\n}\n";
	
  if(FALSE===socket_sendto($socket, $msg, strlen($msg), 0, $broadcast, $port))
  {
    trigger_error('Failed to send message', E_USER_ERROR );
  }

  // We're done
  socket_close( $socket );
?>

</body>
</html>

A PHP script to send an xPL message from the server. The message parameters are passed to the script in the URL's query string. Each time the script is called, a new socket is created and a UDP message broadcast to the network. The $broadcast and $listenOnAddress variables at the top of the file must be changed to match your own xPL network settings.

xpl.js


// Javascript method to trigger the sending of an 
// xPL message via a call to a remote server script 

var IFrameObj; // our IFrame object
function SendXPL( xPLType, xPLTarget, xPLSchema, xPLBody ) 
{
  if( !document.createElement )
  {
    return true
  }

  var IFrameDoc;
  var URL = "xpl.php?type="+xPLType+"&target="+xPLTarget+"&schema="
                                   +xPLSchema+"&body="+xPLBody;

  if (!IFrameObj && document.createElement) {
    // create the IFrame and assign a reference to the
    // object to our global variable IFrameObj.
    // this will only happen the first time 
    // callToServer() is called
   try {

      var tempIFrame=document.createElement('iframe');
      tempIFrame.setAttribute('id','RSIFrame');
      tempIFrame.style.border='0px';
      tempIFrame.style.width='0px';
      tempIFrame.style.height='0px';
      IFrameObj = document.body.appendChild(tempIFrame);
      
      if (document.frames) 
      {
        // this is for IE5 Mac, because it will only
        // allow access to the document object
        // of the IFrame if we access it through
        // the document.frames array
        IFrameObj = document.frames['RSIFrame'];
      }
    } 
    catch(exception) 
    {
      // This is for IE5 PC, which does not allow dynamic creation
      // and manipulation of an iframe object. Instead, we'll fake
      // it up by creating our own objects.
      iframeHTML='\<iframe id="RSIFrame" style="';
      iframeHTML+='border:0px;';
      iframeHTML+='width:0px;';
      iframeHTML+='height:0px;';
      iframeHTML+='"><\/iframe>';
      document.body.innerHTML+=iframeHTML;
      IFrameObj = new Object();
      IFrameObj.document = new Object();
      IFrameObj.document.location = new Object();
      IFrameObj.document.location.iframe = document.getElementById('RSIFrame');
      IFrameObj.document.location.replace = function(location) 
      {
        this.iframe.src = location;
      }
    }
  }
  
  if (navigator.userAgent.indexOf('Gecko') !=-1 && !IFrameObj.contentDocument )
  {
    // we have to give NS6 a fraction of a second
    // to recognize the new IFrame
    setTimeout('SendXPL('+xPLType+','+xPLTarget+','+xPLSchema+','+xPLBody+')',10);
    return false;
  }
  
  if( IFrameObj.contentDocument ) 
  {
    // For NS6
    IFrameDoc = IFrameObj.contentDocument; 
  } 
  else if( IFrameObj.contentWindow )
  {
    // For IE5.5 and IE6
    IFrameDoc = IFrameObj.contentWindow.document;
  }
  else if( IFrameObj.document )
  {
    // For IE5
    IFrameDoc = IFrameObj.document;
  }
  else 
  {
    return true;
  }

  IFrameDoc.location.replace( URL );
  return false;
}

A Javascript function that creates a hidden IFrame for triggering the server-side PHP script without causing the current page to reload. Working with the IFrame is tricky, since the method used depends on the browser. I cannot take credit for the solution; the code comes from this useful article.

noiframe.html


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
  <head>
    <title>xPL Remote Scripting Test</title>
  </head>
  <body>
    <p>Your browser does not support IFRAME Remote Scripting Technology</p>
  </body>
</html>

In the rare case where IFrames are not supported by the browser, this page will report the error to the user.

client.html


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
  <head>
    <title>xPL Remote Scripting Test</title>
    <script type="text/javascript" language="JavaScript" src="xpl.js"></script>
  </head>
  <body>
    <a onclick="return SendXPL( 'xpl-trig', 
                               '*', 
                               'sensor.basic', 
                               'device=rain gauge%0atype=count%0acurrent=27' );"
                               href="noiframe.html">Make remote script call</a>
  </body>
</html>

A minimal web page demonstrating how to send an xPL message; in this example a sensor.basic trigger message from a rain gauge. The crucial line here is the one containing the call to SendXPL. The four parameters supply the message type, the target, the schema, and the message body, in accordance with the xPL protocol specification.

The message body requires some care. Usually the body of an xPL message contains lines of name=value pairs terminated with a linefeed character. The only way to pass these linefeeds in the URL's query string is to use the sequence &0a instead. If you use \n, the message body will contain a backslash and an 'n' rather a linefeed character and the message will be unreadble by other xPL applications. The body should not contain any enclosing brackets, and a final linefeed character should not be appended.

Hopfully that is sufficient to enable you to start sending xPL messages from your own web pages. Any feedback or questions should be posted to the xPL forums.