Luup Plugins ByHand

From MiOS
(Difference between revisions)
Jump to: navigation, search
(New page: ==Introduction to UPnP== UPnP (Universal plug and play) is the industry-standard way to communicate with devices. In UPnP the device that is doing the controlling (ie the remote control)...)
 
Line 1: Line 1:
==Introduction to UPnP==
+
==Description of the XML files==
  
UPnP (Universal plug and play) is the industry-standard way to communicate with devices.  In UPnP the device that is doing the controlling (ie the remote control) is called a UPnP Control PointThe device which can be controlled is the UPnP Device.  You describe the capabilities of a UPnP Device and tell the UPnP Control Points what the device can do by creating 2 types of XML files: 1) a single device specification file which has basic information on your device such as the manufacturer and model, and 2) one or more service specification files which describe a serviceIn UPnP a "service" is a list of variables and actionsVariables describe the current state of the device and actions describe what you can do with the device.   
+
Because the web generator is not yet operational you need to create the XML files by hand.  The easiest way to do this is to start with an existing device as a template that you modify to suit your needs.  In Vera's Setup Web UI, choose Devices, Luup plug-insThere is a list of all the XML files which came with Vera by default.  Files that start with D_ are UPnP device specificationsFiles that start with S_ are UPnP service specificationsAnd files that start with I_ are Luup implementation filesClick a file to view it's contents.
  
For example, a common service is "SwitchPower".  This service describes the ability to turn something on and off.  The main variable in the service is called "Status", and it has a value of 0 if the device is currently off or 1 if it's currently onThe main action in the service is called "SetTarget" which is how you turn the power on or off.  The "SetTarget" action has an argument called "newTargetValue" which can be either 0 or 1 to indicate if the device should be turned on or off.  Variables, then, are outgoing data from the UPnP Device to the UPnP Control Point, and actions are incoming data from the UPnP Control Point to the UPnP DeviceLet's say your UPnP device is a light switch.  The UPnP Control Point is perhaps a touch-screen web pad, and it will display an icon of a light switch to let the user control your UPnP light switch.  The Control Point will first ask the UPnP light switch for the current value of the "Status" variableIf the variable is currently 1, the Control Point will display the light switch in the 'on' positionNow, if the user hits the 'off' button on the UPnP control point it sends the UPnP light switch a "SetTarget" action with the argument "newTargetValue" equals 0.  After the light switch successfully turns off, the variable "Status" will change to 0 so anytime a UPnP control point that wants to know if the light is on or off can ask the light switch for the current value of the "Status" variable.
+
When you open a device specification file the xml tag deviceType defines what kind of device it is.  This is how a UPnP Control Point knows what this device is.  If the device type starts with urn:schemas-upnp-org, that means it's a UPnP defined standard, and the list of services the device must support are defined by the UPnP forumIf you're creating your own device type you can substitute the schemas-upnp-org with your own web domain name and change the other parts of the nameBut stick to the same convention and use only a-z, 0-9 and hypens (-) and colons (:)Remember though that if you use your own device type chances are UPnP Control Points won't know what to do with it unless the author of the control point makes a custom addition for you.
  
In the UPnP device's device description xml file there is a list of the services that the device implements with a cross reference to the service fileWhenever possible a service should be as small and re-usable as possible so other devices can use the same service file without creating the service's again from scratch.  For example, there are both dimmable light switches, and simple on/off switchesBoth kinds of switches support the ability to turn on and off, dimmable ones just add the ability to dim also.  So, the most efficient thing to do is have a service called "SwitchPower" which describes the action to turn the switch on and off, and to have a separate service called "Dimming" which describes the action to dim the light.  The on/off switch will support only the "SwitchPower" service, and the dimmable switch will support both the "SwitchPower" and the "Dimming" serviceThis is better than creating a "Dimming" service which does both on/off and dimming because if you did that, the maker of a UPnP control point would need to implement 2 different actions for the basic on/off.  You do not want to make an individual service do more than one thing.
+
The device file references the service files (S_) and gives each service a serviceType and a serviceId.  The serviceType what defines the standard UPnP service.  But since it's possible to have multiple instances of a given service, each needs a unique serviceId.  For example, there is a standard UPnP service to set the setpoint on a thermostat called: urn:schemas-upnp-org:service:TemperatureSetpoint:1But many thermostats have multiple setpoints, such as heat and cool.  So in the standard for a UPnP thermostat device (D_HVAC_ZoneThermostat1.xml), there are 2 instances of the serviceType "urn:schemas-upnp-org:service:TemperatureSetpoint:1", one has the id "urn:upnp-org:serviceId:TemperatureSetpoint1_Cool" and the other "urn:upnp-org:serviceId:TemperatureSetpoint1_Heat".  They both use the same service specification, S_TemperatureSetpoint1.xml, which is in the XML tag SCPDURL.
  
The UPnP forum is an industry consortium that approves specifications for UPnP devices.  They have already approved standards for SwitchPower, Dimming, and so on.  We will not want to modify these device or service specifications.  We use them as they were ratified by the committee because that is what UPnP Control Points will be expecting.  However there are lots of devices which the UPnP forum has not yet come up with a standard for.  In this case each vendor can come up with his own solution.  However, if there is an existing device+service specification that is already in use, particularly by a large company in the industry, we should use their device+service specifications (ie XML files) whenever possible since there are probably already UPnP Control Points out there that know how to talk to them.
+
The controlURL and eventSubURL are set by the Luup engine and the values in the device specification file are ignored.
  
==Introduction to Luup plugins==
+
The UPnP specification allows that we can add our own custom xml tags.  So we add the xml tags "implemenationList" to the device specification which references the implementation files that device will use.
  
The UPnP specifications define what the XML files look like and how a UPnP Control Point talks to a UPnP Device to get the current Variables and to send the device Actions.  But that's where UPnP ends; it does not define the implementation, which is to say, how the device will implement the action.  Normally UPnP devices write programming code to handle the implementation.  Luup instead lets you store the implementation in a 3rd type of XML file we call the implementation file.  In the implementation file you tell Luup what to do when it receives an action from a UPnP Control Point.  The implementation file can contain infrared codes which Loop will send when the action comes in, binary data which Luup will send out a serial port, or some Lua code that Luup will run.  The Lua code can set the current value of the variables for the device.
+
==The Luup XML implementation file==
  
Luup handles all the complexities of the UPnP protocol.  And the web-based Luup plugin generator handles all the complexities of XML (coming soon).  So all you do is fill in the form which has basic information about your device, like the manufacturer and model, and the generator creates the UPnP Device specification for you.  You can then check off which services your device implements, such as "SwitchPower" if it's something that can be turned on and offIf your device has new capabilities that aren't already in an existing service, the generator let's you create a new service by describing the actions your device implements, like "Brightness Up", or "Sensor Arm", etc.  Lastly, the web generator let's you specify what to do when the action comes in.  This is saved in the implementation file.
+
The top-level XML tag (ie root node) is called "implementation".  It contains the following nodes:
  
==Description of the XML files==
+
===settings===
 +
 
 +
The settings node contains various settings for the implementation.
 +
 
 +
protocol: Is the protocol to use to talk to the device if you'll be sending data over the network or a serial port.  In this sense, the protocol basically just tells Luup what is considered a single "chunk" of data.  For example, the protocol 'crlf' means all incoming commands are terminated with a carriage return+line feed character, and all outgoing data should have a cr+lf appended.  In this way when data comes in you don't need to worry about parsing it byte-by-byte.  Luup will get a "chunk" (one line in this case) and pass it to whatever Lua code you write to handle incoming data.  Other protocols are stx+etx if chunks of data are surrounded by those characters, and so on.  Since your Lua code is much cleaner when it handles things in chunks, if you have a protocol that we don't yet support which is likely to be used by other devices too, let us know and we'll add it to the Luup engine so you don't need to mess with it.
 +
 
 +
ioPort: If the device talks to another device on the internet, this is what port to use.
 +
 
 +
handleChildren: If this flag is 1, then any actions that are sent to one of this device's children will be handled within this devices implementation file.  Look at the I_GC100.xml file to see how this is done.  The GC100 is a parent device which has no services or actions, but has child devices like i/r transmitters and relays.  This implementation file handles the SwitchPower/SetTarget action for the relays, as well as the SendProntoCode action for the ir transmitters, even though those actions will be sent to the GC100's child device--not the device GC100 itself.
 +
 
 +
===functions===
 +
 
 +
Put here the Lua code for functions you want to be able to use in other places in your Lua code.
 +
 
 +
===incoming===
 +
 
 +
This block defines what to do when data is received by the device, if this device is talking on a serial port, ethernet port, or some other generic i/o port.  The Luup engine handles low-level i/o for these types of ports so you only need to call one of the [[Luup_Lua_extensions]] to send data on the port.
 +
 
 +
===startup===
 +
 
 +
This tag is a comma separated list of functions you want the Luup engine to call when it's starting up.  Generally these are functions in the 'functions' tag.
 +
 
 +
===actionList===
 +
 
 +
This is where you specify what to do when an action comes in.  Create an "action" node for each action, which contains "serviceId" and "name" tags to indicate what action is being implemented.  The "name" tag refers to the name of the Action as defined in the UPnP Service Specification file.
 +
 
 +
There are several different nodes you can put within the "action" node.
 +
 
 +
====ir====
 +
 
 +
Put an ir code in pronto format in this tag.  Luup will send a SendProntoCode action to whatever device is specified as the [[Luup_IO_Device]] in the [[Luup_Configuration_File]].
 +
 
 +
====run/job/incoming/timeout====
 +
 
 +
Put Lua code in the 'run' node that is run immediately when the action is received.  This code should be short and quick and return right away because the UPnP Control Point will probably be blocked while it waits for the reply.  Also, your Luup device will not do anything else while the 'run' code is executing.  If it will take some time to handle the action, use the 'job' tag instead.  Job's will run asynchronously, meaning they happen in the background.  If you implement this action in a Job, you won't be able to give the result code (ie success/failure) to the UPnP Control Point because the Luup engine gives the Control Point an "action successful" as soon as it creates the job.  You can put code in both the 'run' and 'job' tags.  In this case, the code in the 'run' tag is run immediately and can return an error condition which is sent back to the UPnP Control Point, or, if it returns 'OK', then Luup will send the 'ok' to the UPnP Control Point and run the job later.  The job can take as long as you want.
 +
 
 +
The UPnP forum did not create an action "on" or "off" for a light switch because they know that it can take some time to actually turn the light on or off, and you don't want to block the UPnP control point waiting for the light to go on or off.  So, the UPnP action to turn a light on is called "SetTarget".  The UPnP action tells the control point it executed the action ok simply when it receives the action.  If the control point wants to know for sure if the light actually turned on, the control point should watch the 'Status' variable and see if it changes to "1" when the light is actually on.
 +
 
 +
In the case of the GC100, (see: I_GC100.xml), on/off of the relays happens immediately.  So the implementation is inside a 'run' tag, which sets the "Status" variables.  In the case of SendProntoCode, the implementation is inside a 'job' tag since the pronto commands may take time.
 +
 
 +
When you have Lua code inside a 'job' tag, Luup will return 'OK' to the Control Point in response to the action, and then queues up the job and runs the Lua in your 'job' code.  The job code returns 2 values: 1) the status of the job, and 2) how long to wait before the job times out in seconds.  The status can be:
 +
 
 +
0=job_WaitingToStart: In vera's UI a job in this state is displayed as a gray icon.  It means it's waiting to start.  If you return this value your 'job' code will be run again in the 'timeout' seconds
 +
 
 +
2=job_Error, or 3=job_Aborted: In vera's UI a job in this state is displayed as a red icon.  This means the job failed.  Your code won't be run again.
 +
 
 +
4=job_Done: In vera's UI a job in this state is displayed as a green icon.  This means the job finished ok.  Your code won't be run again.
 +
 
 +
5=job_WaitingForCallback: In vera's UI a job in this state is displayed as a moving blue icon.  This means the job is running and you're waiting for return data.  Any data that comes in while the job is in this state will go to the lua code in the job's 'incoming' tag.  If no data comes in before the number of seconds you return in the timeout, then the code in the job's 'timeout' tag is run.
 +
 
 +
So in the SendProntoCode job code for the I_GC100, we return return 5,10 which means we're waiting for data and should wait up to 10 seconds.
 +
 
 +
Whenever your job is in status 5=job_WaitingForCallback, whatever data comes in on the i/o port will be first given to the job's 'incoming' Lua code *before* it's given to the general purpose 'incoming' Lua code (see below).  If the incoming data is not for this job, you should not return a 'true' as the 3rd return parameter from 'incoming', and the Luup engine will forward the incoming data to the general purpose 'incoming' Lua code.  If you do return 'true', the Luup engine assumes you handled the incoming data and there's nothing more to do with it.  See the incoming tag for the SendProntoCode job code for the I_GC100 as an example.  The 'incoming' code returns 3 values.  The first 2 are the same as for the 'job', and the 3rd is true or false depending on if the incoming data was for this job or not.
 +
 
 +
The Luup engine will only run one chunk of Lua code at a time.  This is necessary to prevent problems with the shared variables.  However, there can be multiple jobs in the 5=job_WaitingForCallback state.  When one job is run, and it returns the status 5=job_WaitingForCallback, the Luup engine will start another job while the first one is waiting.  So when incoming data is received, the Luup engine will pass the data to the 'incoming' Lua code for all the jobs in the 5=job_WaitingForCallback state, in the order the jobs were created.  As soon as a job returns 'true' for the 3rd parameter, Luup discards the incoming data.  As long as the jobs return 'false' the data is passed on to the next job, and eventually to the general purpose 'incoming' Lua code.  If, when you start a job, you do not want other jobs to run until your job is done, call the [[Luup_Lua_extensions]] lu_block_jobs.  Other Luup plugins will continue to run asynchronously, but no other jobs will run for this particular Luup plugin until this job becomes 'done' or 'error/abort'.
  
Because the web generator is not yet operational you need to create the XML files by hand.  The easiest way to do this is to start with an existing device as a template that you modify to suit your needs. 
 
  
  
Line 23: Line 76:
  
 
A Luup plugin is a UPnP Device that can be controlled by any UPnP Control Point.
 
A Luup plugin is a UPnP Device that can be controlled by any UPnP Control Point.
 +
 +
In the meantime, you will need to create a Luup plugin by hand as explained here: [[Luup_Plugins_ByHand]].

Revision as of 03:51, 12 June 2009

Contents

Description of the XML files

Because the web generator is not yet operational you need to create the XML files by hand. The easiest way to do this is to start with an existing device as a template that you modify to suit your needs. In Vera's Setup Web UI, choose Devices, Luup plug-ins. There is a list of all the XML files which came with Vera by default. Files that start with D_ are UPnP device specifications. Files that start with S_ are UPnP service specifications. And files that start with I_ are Luup implementation files. Click a file to view it's contents.

When you open a device specification file the xml tag deviceType defines what kind of device it is. This is how a UPnP Control Point knows what this device is. If the device type starts with urn:schemas-upnp-org, that means it's a UPnP defined standard, and the list of services the device must support are defined by the UPnP forum. If you're creating your own device type you can substitute the schemas-upnp-org with your own web domain name and change the other parts of the name. But stick to the same convention and use only a-z, 0-9 and hypens (-) and colons (:). Remember though that if you use your own device type chances are UPnP Control Points won't know what to do with it unless the author of the control point makes a custom addition for you.

The device file references the service files (S_) and gives each service a serviceType and a serviceId. The serviceType what defines the standard UPnP service. But since it's possible to have multiple instances of a given service, each needs a unique serviceId. For example, there is a standard UPnP service to set the setpoint on a thermostat called: urn:schemas-upnp-org:service:TemperatureSetpoint:1. But many thermostats have multiple setpoints, such as heat and cool. So in the standard for a UPnP thermostat device (D_HVAC_ZoneThermostat1.xml), there are 2 instances of the serviceType "urn:schemas-upnp-org:service:TemperatureSetpoint:1", one has the id "urn:upnp-org:serviceId:TemperatureSetpoint1_Cool" and the other "urn:upnp-org:serviceId:TemperatureSetpoint1_Heat". They both use the same service specification, S_TemperatureSetpoint1.xml, which is in the XML tag SCPDURL.

The controlURL and eventSubURL are set by the Luup engine and the values in the device specification file are ignored.

The UPnP specification allows that we can add our own custom xml tags. So we add the xml tags "implemenationList" to the device specification which references the implementation files that device will use.

The Luup XML implementation file

The top-level XML tag (ie root node) is called "implementation". It contains the following nodes:

settings

The settings node contains various settings for the implementation.

protocol: Is the protocol to use to talk to the device if you'll be sending data over the network or a serial port. In this sense, the protocol basically just tells Luup what is considered a single "chunk" of data. For example, the protocol 'crlf' means all incoming commands are terminated with a carriage return+line feed character, and all outgoing data should have a cr+lf appended. In this way when data comes in you don't need to worry about parsing it byte-by-byte. Luup will get a "chunk" (one line in this case) and pass it to whatever Lua code you write to handle incoming data. Other protocols are stx+etx if chunks of data are surrounded by those characters, and so on. Since your Lua code is much cleaner when it handles things in chunks, if you have a protocol that we don't yet support which is likely to be used by other devices too, let us know and we'll add it to the Luup engine so you don't need to mess with it.

ioPort: If the device talks to another device on the internet, this is what port to use.

handleChildren: If this flag is 1, then any actions that are sent to one of this device's children will be handled within this devices implementation file. Look at the I_GC100.xml file to see how this is done. The GC100 is a parent device which has no services or actions, but has child devices like i/r transmitters and relays. This implementation file handles the SwitchPower/SetTarget action for the relays, as well as the SendProntoCode action for the ir transmitters, even though those actions will be sent to the GC100's child device--not the device GC100 itself.

functions

Put here the Lua code for functions you want to be able to use in other places in your Lua code.

incoming

This block defines what to do when data is received by the device, if this device is talking on a serial port, ethernet port, or some other generic i/o port. The Luup engine handles low-level i/o for these types of ports so you only need to call one of the Luup_Lua_extensions to send data on the port.

startup

This tag is a comma separated list of functions you want the Luup engine to call when it's starting up. Generally these are functions in the 'functions' tag.

actionList

This is where you specify what to do when an action comes in. Create an "action" node for each action, which contains "serviceId" and "name" tags to indicate what action is being implemented. The "name" tag refers to the name of the Action as defined in the UPnP Service Specification file.

There are several different nodes you can put within the "action" node.

ir

Put an ir code in pronto format in this tag. Luup will send a SendProntoCode action to whatever device is specified as the Luup_IO_Device in the Luup_Configuration_File.

run/job/incoming/timeout

Put Lua code in the 'run' node that is run immediately when the action is received. This code should be short and quick and return right away because the UPnP Control Point will probably be blocked while it waits for the reply. Also, your Luup device will not do anything else while the 'run' code is executing. If it will take some time to handle the action, use the 'job' tag instead. Job's will run asynchronously, meaning they happen in the background. If you implement this action in a Job, you won't be able to give the result code (ie success/failure) to the UPnP Control Point because the Luup engine gives the Control Point an "action successful" as soon as it creates the job. You can put code in both the 'run' and 'job' tags. In this case, the code in the 'run' tag is run immediately and can return an error condition which is sent back to the UPnP Control Point, or, if it returns 'OK', then Luup will send the 'ok' to the UPnP Control Point and run the job later. The job can take as long as you want.

The UPnP forum did not create an action "on" or "off" for a light switch because they know that it can take some time to actually turn the light on or off, and you don't want to block the UPnP control point waiting for the light to go on or off. So, the UPnP action to turn a light on is called "SetTarget". The UPnP action tells the control point it executed the action ok simply when it receives the action. If the control point wants to know for sure if the light actually turned on, the control point should watch the 'Status' variable and see if it changes to "1" when the light is actually on.

In the case of the GC100, (see: I_GC100.xml), on/off of the relays happens immediately. So the implementation is inside a 'run' tag, which sets the "Status" variables. In the case of SendProntoCode, the implementation is inside a 'job' tag since the pronto commands may take time.

When you have Lua code inside a 'job' tag, Luup will return 'OK' to the Control Point in response to the action, and then queues up the job and runs the Lua in your 'job' code. The job code returns 2 values: 1) the status of the job, and 2) how long to wait before the job times out in seconds. The status can be:

0=job_WaitingToStart: In vera's UI a job in this state is displayed as a gray icon.  It means it's waiting to start.  If you return this value your 'job' code will be run again in the 'timeout' seconds
2=job_Error, or 3=job_Aborted: In vera's UI a job in this state is displayed as a red icon.  This means the job failed.  Your code won't be run again.
4=job_Done: In vera's UI a job in this state is displayed as a green icon.  This means the job finished ok.  Your code won't be run again.
5=job_WaitingForCallback: In vera's UI a job in this state is displayed as a moving blue icon.  This means the job is running and you're waiting for return data.  Any data that comes in while the job is in this state will go to the lua code in the job's 'incoming' tag.  If no data comes in before the number of seconds you return in the timeout, then the code in the job's 'timeout' tag is run.

So in the SendProntoCode job code for the I_GC100, we return return 5,10 which means we're waiting for data and should wait up to 10 seconds.

Whenever your job is in status 5=job_WaitingForCallback, whatever data comes in on the i/o port will be first given to the job's 'incoming' Lua code *before* it's given to the general purpose 'incoming' Lua code (see below). If the incoming data is not for this job, you should not return a 'true' as the 3rd return parameter from 'incoming', and the Luup engine will forward the incoming data to the general purpose 'incoming' Lua code. If you do return 'true', the Luup engine assumes you handled the incoming data and there's nothing more to do with it. See the incoming tag for the SendProntoCode job code for the I_GC100 as an example. The 'incoming' code returns 3 values. The first 2 are the same as for the 'job', and the 3rd is true or false depending on if the incoming data was for this job or not.

The Luup engine will only run one chunk of Lua code at a time. This is necessary to prevent problems with the shared variables. However, there can be multiple jobs in the 5=job_WaitingForCallback state. When one job is run, and it returns the status 5=job_WaitingForCallback, the Luup engine will start another job while the first one is waiting. So when incoming data is received, the Luup engine will pass the data to the 'incoming' Lua code for all the jobs in the 5=job_WaitingForCallback state, in the order the jobs were created. As soon as a job returns 'true' for the 3rd parameter, Luup discards the incoming data. As long as the jobs return 'false' the data is passed on to the next job, and eventually to the general purpose 'incoming' Lua code. If, when you start a job, you do not want other jobs to run until your job is done, call the Luup_Lua_extensions lu_block_jobs. Other Luup plugins will continue to run asynchronously, but no other jobs will run for this particular Luup plugin until this job becomes 'done' or 'error/abort'.


 Therefore you do not need 

A Luup plugin is a UPnP Device that can be controlled by any UPnP Control Point.

In the meantime, you will need to create a Luup plugin by hand as explained here: Luup_Plugins_ByHand.

Personal tools