Author Topic: Luup plugins and bi-directional communication with a remote server  (Read 4229 times)

Offline Ap15e

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 1998
  • Karma: +12/-0
I am trying to write a plugin which communicates with a remote Squeezebox Server.

Definition file:

Quote
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
  <specVersion>
    <major>0</major>
    <minor>1</minor>
  </specVersion>
  <device>
    <deviceType>urn:schemas-ap15e-com:device:SqueezeboxUI:1</deviceType>
    <friendlyName>Squeezebox UI for Vera</friendlyName>
    <manufacturer>Ap15e</manufacturer>
    <manufacturerURL>http://www.ap15e.com</manufacturerURL>
    <modelDescription>Line-oriented UI for Vera using Squeezebox Server CLI via TCP port 9090</modelDescription>
    <modelName>LOUIS4Vera (Line-Oriented User Interface on Squeezebox)</modelName>
    <modelNumber>0.1</modelNumber>
    <UDN>uuid:upnp-ap15e-1_0-0000000000001</UDN>
    <protocol>raw</protocol>
    <handleChildren>0</handleChildren>
    <implementationList>
      <implementationFile>I_LOUIS4Vera1.xml</implementationFile>
    </implementationList>
  </device>
</root>

Implementation file (version 1):

Quote
<?xml version="1.0" encoding="UTF-8"?>
<implementation>
    <settings>
        <protocol>raw</protocol>
    </settings>
    <functions>
        function LOUIS4VeraStartup(DevNo)
         luup.log("LOUIS4Vera: Starting ... device #"..tostring(DevNo).." starting up with id "..luup.devices[DevNo].id)
         local command = '00:04:20:XX:XX:XX subscribe button\n'
         if (luup.io.write(command, DevNo) == false)
          then
           luup.log("LOUIS4Vera: Cannot send command " .. command .. " communications error", 1)
--luup.set_failure(true)
          end
        end
    </functions>
    <incoming>
<lua>
          luup.log("LOUIS4Vera: Incoming " .. tostring(lul_data))
</lua>
    </incoming>
    <startup>LOUIS4VeraStartup</startup>
</implementation>

Implementation file (version 2):

Quote
<?xml version="1.0" encoding="UTF-8"?>
<implementation>
    <settings>
        <protocol>raw</protocol>
    </settings>
    <functions>
        function LOUIS4VeraStartup(DevNo)
         luup.log("LOUIS4Vera: Starting ... device #"..tostring(DevNo).." starting up with id "..luup.devices[DevNo].id)
local SBS_IP_address       = '192.168.178.22'
local playerid          = '00:04:20:XX:XX:XX'
local SBS_CLI_port      = 9090
socket=require('socket')
local SBS_socket_timeout      = 0.1
local client = socket.connect( SBS_IP_address, SBS_CLI_port )
client:settimeout( SBS_socket_timeout )
client:send(playerid..' subscribe button\n')
        end
    </functions>
    <incoming>
<lua>
          luup.log("LOUIS4Vera: Incoming " .. tostring(lul_data))
</lua>
    </incoming>
    <startup>LOUIS4VeraStartup</startup>
</implementation>

Version 1 (luup.io.write) does not work (from Squeezebox Server log, reversed timeline):

Quote
29208: [10-05-28 15:20:23.1742] Slim::Plugin::CLI::Plugin::client_socket_close (296) Closed connection with 192.168.178.34:4338 (0 active connections)
29207: [10-05-28 15:20:23.1640] Slim::Plugin::CLI::Plugin::client_socket_close (278) Begin Function
29206: [10-05-28 15:20:23.1574] Slim::Plugin::CLI::Plugin::client_socket_read (332) Connection with 192.168.178.34:4338 half-closed by peer
29205: [10-05-28 15:20:23.1506] Slim::Plugin::CLI::Plugin::client_socket_read (307) Begin Function

Version 2 (socket.connect) does work (from Squeezebox Server log, reversed timeline):

Quote
29143: [10-05-28 15:18:21.0200] Slim::Plugin::CLI::Plugin::client_socket_buf_parse (380) 192.168.178.34:4338
29142: ]
29141: [10-05-28 15:18:21.0121] Slim::Plugin::CLI::Plugin::client_socket_read (341) 192.168.178.34:4338 - Buffered [00:04:20:XX:XX:XX subscribe button
29140: [10-05-28 15:18:21.0050] Slim::Plugin::CLI::Plugin::client_socket_read (307) Begin Function
29139: [10-05-28 15:18:20.9881] Slim::Plugin::CLI::Plugin::cli_socket_accept (257) Accepted connection from 192.168.178.34:4338 (1 active connections)
29138: [10-05-28 15:18:20.9641] Slim::Plugin::CLI::Plugin::cli_socket_accept (225) Begin Function

The <incoming> block never gets called (neither with version 1 nor with version 2).

Questions:

  • What is the difference between luup.io.write and direct communication via a socket?
  • Why doesn't the <incoming> block work?
  • Which syntax is the correct one: <incoming><lua>[Lua code]</lua></incoming> or <incoming>[Lua code]</incoming>?

Offline Ap15e

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 1998
  • Karma: +12/-0
Re: Luup plugins and bi-directional communication with a remote server
« Reply #1 on: May 29, 2010, 09:12:36 am »
To answer questions 1 & 2 myself:

luup.io.write(command) without luup.io.open does not work. May be caused by an IP address with a port number (IP is 192.168.178.22:9090, so Vera might fail to get the socket connection right). New MCV bug #1105.

Let's use luup.io.open - but luup.devices[DevNo].id fails to return the IP address. New MCV bug #1106.

We have to resort to user-defined variables SBS_IP_Address and SBS_CLI_Port ...

The following code does work (even the <incoming> block gets called):

Quote
<?xml version="1.0" encoding="UTF-8"?>
<implementation>
    <settings>
        <protocol>crlf</protocol>
    </settings>
    <functions>
        function LOUIS4VeraStartup( deviceId )

         selfId = 'urn:schemas-ap15e-com:deviceId:SqueezeboxUI1'
         
         variables_set = true;

         SBS_IP_address = luup.variable_get( selfId, "SBS_IP_address", deviceId )
         if ( SBS_IP_address == nil ) or ( SBS_IP_address == '' )
          then
           luup.variable_set( selfId, "SBS_IP_address", '', deviceId)
           variables_set = false
          end

         SBS_CLI_port = luup.variable_get( selfId, "SBS_CLI_port", deviceId )
         if ( SBS_CLI_port == nil ) or ( SBS_CLI_port == '' )
          then
           luup.variable_set( selfId, "SBS_CLI_port", '', deviceId)
           variables_set = false
          end

         SBS_playerid = luup.variable_get( selfId, "SBS_playerid", deviceId )
         if ( SBS_playerid == nil ) or ( SBS_playerid == '' )
          then
           luup.variable_set( selfId, "SBS_playerid", '', deviceId)
           variables_set = false
          end

         luup.log("LOUIS4Vera: Starting ... device #"..tostring(deviceId).." starting up with id "..luup.devices[deviceId].id)

         if variables_set
          then
           luup.io.open( SBS_handle, SBS_IP_address, SBS_CLI_port )
           command = SBS_playerid ..' subscribe button\n'
           res = luup.io.write(command, SBS_handle )
           if ( res == false ) or ( res == nil )
            then
             luup.log( "LOUIS4Vera: Cannot send command " .. command .. " - communication error" )
             luup.task( "Communication with SBS doesn't work. Is SBS running?", 2, "LOUIS4Vera", -1 )
             luup.set_failure( true, deviceId )
             return false
            end
          else
           luup.log( "LOUIS4Vera: Variables SBS_IP_address, SBS_CLI_port, SBS_playerid not set" )
           luup.task( "Set the variables SBS_IP_address, SBS_CLI_port, SBS_playerid.", 2, "LOUIS4Vera", -1 )
           luup.set_failure( true, deviceId )
           return false
          end
        end
    </functions>
    <incoming>
        <lua>
          luup.log("LOUIS4Vera: Incoming " .. tostring(lul_data))
        </lua>
    </incoming>
    <startup>LOUIS4VeraStartup</startup>
</implementation>

Question 3 remains open, but <lua> </lua> seems to work ...

Offline guessed

  • Master Member
  • *******
  • Posts: 5294
  • Karma: +90/-22
  • Release compat is not a bolted-on afterthought
Re: Luup plugins and bi-directional communication with a remote server
« Reply #2 on: May 29, 2010, 06:02:16 pm »
@Ap15e,
When I'm doing TCP stuff, I use:

    luup.io.open(lul_device, ipAddress, ipPort)

as in seen in my Onkyo plugin:

    http://code.mios.com/trac/mios_onkyo-media-control/browser/I_OnkyoReceiver1.xml#L71

I don't think you can format the IP Address attribute as "<ip>:<port>" in the Vera UI (but I could be wrong)

Note that luup.devices[...].id is just the DeviceId, you want luup.devices[...].ip, which is the [standard] IP Address variable that's presented in Advanced.

The Onkyo driver is working fine, so it's probably a good sample to look at for this type of stuff.


Generally the difference between model 1 and model 2 is that in Model 1, Vera looks after maintaining the connection and you handle the data.  In Model 2, you handle both the connection and the data.

Also, in Model 1, Vera does some rudimentary chunking of data read using the "boundaries" specified in the <protocol> tag.  If protocol=cr, you get a line's worth of data (and the CR is stripped out).  If protocol=stxetx, then you get all data between the STX and ETX characters.  For RAW, you get every byte on the stream, one call (to the code in the <incoming><lua> block) for every byte.

Similarly, when using luup.io.write() calls, it wrappers the data with the <protocol> delimiter


Offline Ap15e

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 1998
  • Karma: +12/-0
Re: Luup plugins and bi-directional communication with a remote server
« Reply #3 on: May 29, 2010, 08:48:13 pm »
Quote
I don't think you can format the IP Address attribute as "<ip>:<port>" in the Vera UI (but I could be wrong)

It does work for my Squeezebox cover art hack:
http://forum.micasaverde.com/index.php?topic=3604.0

I think Vera does not check for the correct syntax of IP addresses, so it depends on the context whether "<ip>:<port>" does work or not.

Quote
Note that luup.devices[...].id is just the DeviceId, you want luup.devices[...].ip, which is the [standard] IP Address variable that's presented in Advanced.

Yes, you are right.

My plugin is working fine now.