Pass an event to a plugin from outside Indigo

Posted on
Fri Sep 13, 2013 12:48 am
berkinet offline
User avatar
Posts: 3290
Joined: Nov 18, 2008
Location: Berkeley, CA, USA & Mougins, France

Pass an event to a plugin from outside Indigo

Is there a built-in way to pass an event to a plugin from outside of Indigo, possibly from the same machine the server is running on, possibly over the network?

Something like the RESTful API would probably work, but it does not support Custom device types. I could create an Action group and then execute that RESTfuly , but that approach seems to miss the point of a plugin. Similarly, I could change a variable's state RESTfuly and just monitor the variable in a plugin thread -- at least this approach can be installed and maintained without user involvement -- unless they delete the variable. Finally, I could add a TCP listener in the plugin -- and that may be what I end up doing. But, its extra work and it would be nice to have a generic facility available in Indigo -- like maybe a RESTful call to change the state of a device (the state might have to be created with special permission allowing it to be manipulated from outside Indigo).

BTW, the use case here in the apcupsd daemon which monitors UPSs. apcupsd is normally polled for UPS status - which is what the apcupsd plugin does. But, certain UPS events can trigger apcupsd to execute a user provided script or program. That would allow certain events (Power loss, UPS communication failure, etc.) to immediately trigger a plugin event without having to wait for the next scheduled poll to discover the change.

Posted on
Fri Sep 13, 2013 7:50 am
matt (support) offline
Site Admin
User avatar
Posts: 21417
Joined: Jan 27, 2003
Location: Texas

Re: Pass an event to a plugin from outside Indigo

On the Mac running Indigo Server you can define actions in your plugin and then call those using the indigohost shell command. See the Scripting Indigo Plugins section for some details. Here is an example that will play/pause my Indigo iTunes device:

Code: Select all
indigohost -e '
itunesId = "com.perceptiveautomation.indigoplugin.itunes"
itunesPlugin = indigo.server.getPlugin(itunesId)
if itunesPlugin.isEnabled():
  itunesPlugin.executeAction("playPause", deviceId=911369708)
'

Image

Posted on
Fri Sep 13, 2013 7:59 am
berkinet offline
User avatar
Posts: 3290
Joined: Nov 18, 2008
Location: Berkeley, CA, USA & Mougins, France

Re: Pass an event to a plugin from outside Indigo

Great! Exactly what I needed. Thanks Matt.

Posted on
Fri Sep 13, 2013 8:25 am
jay (support) offline
Site Admin
User avatar
Posts: 18220
Joined: Mar 19, 2008
Location: Austin, Texas

Re: Pass an event to a plugin from outside Indigo

Scripting plugins is definitely one option. It seems that events are written to syslog - so you could have your plugin monitor syslog for the events and fire Indigo events as appropriate. That would require little (no?) extra configuration I think.

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Posted on
Fri Sep 13, 2013 9:48 am
berkinet offline
User avatar
Posts: 3290
Joined: Nov 18, 2008
Location: Berkeley, CA, USA & Mougins, France

Re: Pass an event to a plugin from outside Indigo

Matt... any reason you can think of why the indigoPluginHost shell works as me but not when executed by root? As root, it just hangs.

Posted on
Fri Sep 13, 2013 10:18 am
matt (support) offline
Site Admin
User avatar
Posts: 21417
Joined: Jan 27, 2003
Location: Texas

Re: Pass an event to a plugin from outside Indigo

The plugin host has to be run under the same OS X account that launched Indigo Server. Looks like we need to improve the error reporting when this occurs...

Image

Posted on
Sun Sep 15, 2013 1:36 am
berkinet offline
User avatar
Posts: 3290
Joined: Nov 18, 2008
Location: Berkeley, CA, USA & Mougins, France

Re: Pass an event to a plugin from outside Indigo

Not only did Indigo not report the error, IndigioPluginHost wedged, though I was able to kill it with a -9.

Now, in case others are following this topic, I have resolved the problem and have this working. Ideally, all I should have needed to do was set-user-ID-on-execution bit. But, while chmod supports that, OS X/darwin does not actually do anything. The solution was to write a short program in C that sets its uid (setuid)appropriately (I.e. to that of the user running the Indigo server) and then executes a shell script with the embedded IndigioPluginHost call. This works because the C setuid call is functional in OS X. I generalized the code so the uid, path to the script/program to be executed, args for that executable and and Indigo device ID are set in command line args. The path, args and devId are then passed to the executable defined in the path and run as the uid.

I organized this to meet my needs and it might not work as written for others. For example, if there is no -a argument provided, the program will fail when it tries to create the command to be executed by system. Note also, the compiler complains about an incompatible implicit declaration of built-in function ‘memset’ . But, it compiles.

I can provide more information. But, it is likely that anyone needing to do what I did is probably a much more accomplished C programmer and should be helping me rather than vis-a-versa.

Here is the code. Any improvements are most welcome.
Code: Select all
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main ( int argc, char **argv )
{
   int uvalue = 0;
   char *avalue = NULL;
   char *dvalue = NULL;
   char *pvalue = NULL;
   int vflag = 0;
   int index;
   int a;

   opterr = 0;

   while ((a = getopt (argc, argv, "a:u:p:d:vh")) != -1)
     switch (a)
       {
       case 'h':
         fprintf (stdout, "-u uid -p path -a args -d devid -v verbose -h help\n");
         return 0;
       case 'v':
         vflag = 1;
         break;
       case 'd':
         dvalue = optarg;
         break;
       case 'a':
         avalue = optarg;
         break;
       case 'u':
         uvalue = atoi(optarg);
         break;
       case 'p':
         pvalue = optarg;
         break;
       case '?':
         if (optopt == 'c')
           fprintf (stderr, "Option -%c requires an argument.\n", optopt);
         else if (isprint (optopt))
           fprintf (stderr, "Unknown option `-%c'.\n", optopt);
         else
           fprintf (stderr,
                    "Unknown option character `\\x%x'.\n",
                    optopt);
         return 1;
       default:
         abort ();
       }

    if ( vflag == 1)
       printf ("uid = %i, path = %s, action=%s, devid=%s\n", uvalue, pvalue, avalue, dvalue);

   setuid( uvalue );
   char cmd[512];
   memset(cmd, '\0', sizeof(512));
   sprintf(cmd, "%s %s %s", pvalue, avalue, dvalue);
   system( cmd );

   return 0;
}


For reference, the script I am executing, the actual interface to Indigo, is:
Code: Select all
#!/bin/sh

ACTION=$1
DEVID=$2

cd /Library/Application\ Support/Perceptive\ Automation/Indigo\ 6/IndigoPluginHost.app/Contents/MacOS

./IndigoPluginHost -e "
apcupsdPlugin = indigo.server.getPlugin(\"com.berkinet.apcupsd\")
if apcupsdPlugin.isEnabled():
   apcupsdPlugin.executeAction(\"apcupsdServerEvent\", deviceId=${DEVID}, props={\"actionType\":\"${ACTION}\"})
# indigo.server.log(\"Completed Action Call\", type=\"apcupsd\")
"

Note, I did the cd because getting the IndigoPluginHost path into a variable, or executing the full path was a royal pain because of the embedded spaces. I hard coded the action to be called since the plugin action handler looks at the actionType property to determine what to do. This helped me avoid creating a bunch of actions and is, therefore, more easily modified.

One more point, to get your UID, in a terminal window use the id command.
    $ id
    uid=501(rdp) gid=501(rdp) groups=501(rdp),401(com.apple.access_screensharing),12(everyone),33(_appstore),61(localaccounts),80(admin),98(_lpadmin),100(_lpoperator),204(_developer)

Posted on
Sun Sep 15, 2013 3:57 am
berkinet offline
User avatar
Posts: 3290
Joined: Nov 18, 2008
Location: Berkeley, CA, USA & Mougins, France

Re: Pass an event to a plugin from outside Indigo

Found a few interesting things with the use of IndigoPluginHost.

I had defined the -e arg as Matt had described, starting with:
    ./IndigoPluginHost -e "
    apcupsdPlugin = indigo.server.getPlugin(\"com.berkinet.apcupsd\")
    if apcupsdPlugin.isEnabled():
    ...
There is one difference, I used " instead of ' so I could get shell variable expansion.

However, it seems, for some reason, that unless I start the Python code on the same line as the -e, the script hangs after executing.
This is what ps showed when I had a newline after the -e:
Code: Select all
27507   ??  S      0:00.70 ./IndigoPluginHost -e ^JapcupsdPlugin = indigo.server.getPlugin("com.berkinet.apcupsd")^Jif apcupsdPlugin.isEnabled():^J   try:^J      apcupsdPlugin.executeAction("apcupsdServerEvent", deviceId=145579207, props={"actionType":"commfailure"})^J   except:^J      exit(1)^J# indigo.server.log("Completed Action Call", type="apcupsd")^Jexit(0)^J


Also, in attempting to find the cause of the hangs, I added an exit(0) as a last line of the Python cade. But, it seems Indigo does not like that, it throws:
    Script Error Error in plugin execution compileExecuteSource:

    Traceback (most recent call last):
    File "plugin.py", line 211, in compileExecuteSource
    File "plugin.py", line 191, in _compileExecuteSource
    File "<string>", line 11, in _dynamicFunc
    File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/site.py", line 251, in __call__
    <type 'exceptions.SystemExit'>: 0

Removing the exit(0) and placing the start of the Python code immediately after the -e works fine. Here is the code:
Code: Select all
#!/bin/sh

ACTION=$1
DEVID=$2

cd /Library/Application\ Support/Perceptive\ Automation/Indigo\ 6/IndigoPluginHost.app/Contents/MacOS

./IndigoPluginHost -e "apcupsdPlugin = indigo.server.getPlugin(\"com.berkinet.apcupsd\")
if apcupsdPlugin.isEnabled():
   try:
      apcupsdPlugin.executeAction(\"apcupsdServerEvent\", deviceId=${DEVID}, props={\"actionType\":\"${ACTION}\"})
   except:
      indigo.server.log(\"Completed Action Call\", type=\"apcupsd\", isError=True)
      exit(1)
#exit(0)
"
exit 0

Posted on
Sun Sep 15, 2013 8:30 am
berkinet offline
User avatar
Posts: 3290
Joined: Nov 18, 2008
Location: Berkeley, CA, USA & Mougins, France

Re: Pass an event to a plugin from outside Indigo

berkinet wrote:
...it seems, for some reason, that unless I start the Python code on the same line as the -e, the script hangs after executing.

Nope, that did not fix the problem.

However, the problem appears to be in the setuid program, not the shell script. More soon.

Posted on
Sun Sep 15, 2013 9:16 am
berkinet offline
User avatar
Posts: 3290
Joined: Nov 18, 2008
Location: Berkeley, CA, USA & Mougins, France

Re: Pass an event to a plugin from outside Indigo

It seems that the problem only happens when the setuid control program is run from inside a shell script that is executed by apcupsd. I.e. I can execute the same scripts from the shell, as root, and control returns immediately. I can trace the hang back to IndigoPluginHost -- that is, when I place the IndigoPluginHost command in the background (&) control returns immediately back to apcupsd - but, IndigoPluginHost remains, it still does not close. I have no idea what is different in the environment when called by apcupsd. FWIW, apcupsd uses /bin/sh to run the scripts, that is I can see
/bin/sh /etc/apcupsd/commok arg arg... in the ps. I tried that as well, but it works when I do it.

HELP!

Posted on
Sun Sep 15, 2013 10:34 am
matt (support) offline
Site Admin
User avatar
Posts: 21417
Joined: Jan 27, 2003
Location: Texas

Re: Pass an event to a plugin from outside Indigo

I really haven't a clue on this one. Check the console log -- are there any errors from IndigoPluginHost being logged?

Image

Posted on
Sun Sep 15, 2013 11:20 am
jay (support) offline
Site Admin
User avatar
Posts: 18220
Joined: Mar 19, 2008
Location: Austin, Texas

Re: Pass an event to a plugin from outside Indigo

So I guess you decided watching syslog was more trouble than all this? :)

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Posted on
Sun Sep 15, 2013 2:37 pm
berkinet offline
User avatar
Posts: 3290
Joined: Nov 18, 2008
Location: Berkeley, CA, USA & Mougins, France

Re: Pass an event to a plugin from outside Indigo

(Ignoring Jay for the moment :P )

I fixed it. Very weird. When an event occurs, apcupsd uses execv() to run the apccontrol shell script that runs the shell script (the event shell script) that contains the call to my setuid program that runs the shell that talks to Indigo (whew)

apcupsd (daemon) execv -> apccontrol (shell script) -> event_handler_script (shell script) -> setuid program -> shell script to call IndigoPluginHost

I called all the parts directly, and everything worked. The only part I could not test was the execv call. So, I wrote my own program to use execv to call apccontrol, it worked. So, I have absolutely no idea why it works every possible way except when it runs in the real world application (I.e. when apcupsd execs apccontrol).

So, mostly out of desperation, in the event shell script (called by apccontrol) I changed:
#!/bin/sh
to
#!/bin/ksh

It worked. I have absolutely no idea why, and for the moment I don't care.

Posted on
Mon Sep 16, 2013 3:45 am
berkinet offline
User avatar
Posts: 3290
Joined: Nov 18, 2008
Location: Berkeley, CA, USA & Mougins, France

Re: Pass an event to a plugin from outside Indigo

jay (support) wrote:
So I guess you decided watching syslog was more trouble than all this? :)

syslog would have worked. Mostly I wanted to see if I could get something truly event driven rather than event responsive. I could also have created a little shell script to tail -f | grep <event messages from apcupsd> the log and matching lines into the plugin via IndigoPluginHost.

EDIT: And... I could have called IndigoPluginHost from syslog.conf if I defined a facility to process apcupsd messages.

Posted on
Mon Sep 16, 2013 8:56 am
jay (support) offline
Site Admin
User avatar
Posts: 18220
Joined: Mar 19, 2008
Location: Austin, Texas

Re: Pass an event to a plugin from outside Indigo

While it's perhaps an interesting thought experiment (or even an ok one-off solution), seems to me that it's a pretty complicated and fragile solution (lots of moving parts) when simple file I/O would have provided the solution with fewer moving parts. Particularly for a solution that multiple people are going to try to install and run - KISS is almost always the right principle when developing solutions for distribution.

But maybe that's just me... ;)

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Who is online

Users browsing this forum: No registered users and 2 guests