PDA

View Full Version : Girder 6 Plugin Writing - 1



Ron
April 30th, 2015, 07:14 PM
This is a work in progress. I'd like to document the plugin system and adjust the doc based on your feedback.

Introduction

Girder 6 Plugins
Girder plugins can be written without the use of any compiler by using the two built-in scripting languages. This allows anyone to add their plugins to Girder without any additional investment in development software. The two languages used are Lua for the back-end and Javascript for the front-end.

Girder Architecture
Girder is built as a client-server system. That means that the back-end and the front-end are completely separate and communicate via Sockets ( TCP/IP or otherwise ). This split allows a lot of cool things to happen like running Girder on a Raspberry Pi while accessing it from a Windows machine. Also if a front-end crashes the back-end is unaffected. This of course is great for automation. This split however brings some additional work with it for plugin writers as they have to also pass messages back-and-forward. Don't fear however, passing messages is easy and transparent.

Goal
The goal of this document is to get started with a simple plugin that exposes the functionality of a PIO-1 or Global Cache like device. Resources. The Girder 6 manual (http://www.promixis.com/man/g6/?scripting.html) has a lot of detail on the various libraries available. Of special interest will be the transport functionality (http://www.promixis.com/man/g6/?transport.html) as a lot of plugins will be using this.

Ron
May 1st, 2015, 01:44 PM
.plugin files
Girder find your plugins by scanning it's Lua subdirectory for files with the extension .plugin. The syntax of this file is a Lua script. Girder expects the following keys in this file.



name
description
backEndComponentManager
backEndScriptName
frontEndComponentManager
frontEndIncludes
frontEndScriptName
id


name
name is a string, which is displayed in the settings dialog

description
description is a string that is also displayed in the settings dialog.

backEndComponentManager
this is a boolean that indicates if this plugin uses a component manager. Plugins that export devices to the device manager will answer 'true' here otherwise 'false'.

backEndScriptName
This is the name of the lua file that holds the definition of the backend part of the plugin. This part is written in Lua. Note that Lua uses . (dots) to separate directories. For example if your plugin lives in the folder Lua/examples/dmPlugin/plugin.lua you'd enter



backEndScriptName = 'examples.dmPlugin.plugin'


Since Girder runs on platforms with case sensitive filenames please make sure to match your letter case exactly.

frontEndComponentManager
This is the companion to backEndComponentManager, typically you'll have a frontEndComponentManager if you have a backEnd one. So answer 'true' here if you do otherwise 'false'

frontEndIncludes
This is a list of strings containing the additional javascript files that should be loaded into the script state. Note that here you should use (forward) slashes for the path separators. For example if you have an additional javascript file called Lua/Examples/dmPluign/helpers.js you type



frontEndIncludes = { "Lua/Examples/dmPluign/helpers.js" }


frontEndScriptName
This is the file that contains the plugin part of the front-end in javascript. Again here we use slashes to separate folder names.

id
The id is a number that Girder uses to track your plugin. You can request a plugin number here (http://www.promixis.com/forums/showthread.php?21752-Girder-Device-ID-Registry). These numbers are free.

Example

Here is an example .plugin file which belongs to the examples/dmplugin plugin.


name = "Device Manager Example"
description = "Device Manager Example driver"
backEndComponentManager = true
backEndScriptName = "examples.dmplugin.plugin"
frontEndComponentManager = true
frontEndIncludes = { }
frontEndScriptName = "examples/dmplugin/plugin.js"
id=104546

Ron
May 1st, 2015, 02:47 PM
Plugins have a few functions that need to be implemented for Girder to be able to use them. On a high level that is



create/destroy plugin
get actions
get component manager
start / stop


Let's have a look at a back-end skeleton script. It's doesn't do anything -but- it's a valid plugin.



local base = require("plugin.plugin")

module(...)

base:subclass(_M)

function start ( self )
base.start(self)
end

function stop ( self )
base.stop(self)
end

function inbound ( self, id, msgId, data )
end

function init ( self, plugin )
base.init(self)
self.plugin = plugin
self._actions = {}
instance = self
end

function deinit( self )
base.deinit(self)
self._actions = {}
instance = nil
end


The first thing you see is the require('plugin.plugin'). That is the base class of which we inherit. If you are curious go an open that file. It's pretty small. The plugin.plugin class itself is based off 'Class.lua'. The two functions that come from that class are init and deinit. Deinit is called when the plugin is unloaded and init when it is loaded. The first parameter is the self parameter which holds all the member variables for this instance of the object. The init function also gets a parameter called "plugin". This is the encapsulated C++ plugin object of type IGirderDevice.

start and stop tell the plugin that it's OK to start generating events or to stop generating events. If you have no need for them, you don't even need to implement them as they are in the base class. the actions function returns a array of available actions.

Ron
May 1st, 2015, 02:47 PM
Communication
As we have mentioned previously the front-end and the back-end of a plugin could potentially be running on opposite sides of the world. So plugins need a way to communicate between the front-end ( the user facing side ) and the back-end. This is where inbound and self.plugin.sendOutbound come in. Both function has three important parameters, id, msgId and data. The first is the plugin id of the plugin you'd like to send this message to. The second parameter msgId you can freely choose and is for use by your plugin. The third parameter is a string that will hold whatever message data you are sending. We recommend to use JSON messages.



self.plugin.sendOutbound( self.plugin.id, 100, "{ \"hello\": 200; }")


NOT FINISHED -> SHOULD COME LATER IN EXPLANATION

Ron
May 9th, 2015, 09:35 AM
Actions
Each plugin can have several actions. These are the actual pieces that do things inside Girder. There are two parts to actions front-end part and the back-end part. Let's look at the backend part first.

Back-end Action
The back end action is derived from plugin.action and has the following structure:



local base = require('plugin.action')
local Promixis = Promixis
local print = print

module (...)

base:subclass( _M)

subType = 1
nodeType = Promixis.BaseNode.NodeType.COMMAND_NODE

function triggerCommand ( self, command, event )
print("Example Action Trigger")
print(command.action.sValue1)
print(command.action.sValue2)

end

function init ( self )
base.init(self)
end

function deinit( self )
base.deinit(self)
end


init and deinit are the boiler plate class constructor and destructors. The meat lives inside triggerCommand. Trigger command gets two parameters, command and event. Command is the data that is setup for the action by the front-end action (we'll see that below). The event parameter holds the event that triggered this action. NOTE the event can be nil if the action was triggered by the "Run" menu item inside Girder.

The command has the following properties


Name

Type
Description



actionType

number

this is equal to the plugin id



actionSubType

number

this is the id of this particular action, choose freely



action

Action Object

this holds the actual action parameters



target

Target Object

this holds the targeting parameters



state

State Object

this holds the state object




The action object is the one that we are mostly interested in. It has the following properties:



Name

Type

Description



sValue[1,2,3]

string
sValue1, 2 and 3 parameter use is defined by your plugin



iValue[1,2,3]

string

iValue1, 2 and 3 parameter use is defined by your plugin



bValue[1,2,3]

string
iValue1, 2 and 3 parameter use is defined by your plugin



lValue[1,2,3]

number

this holds the link id in case your action refers to another node on the tree



fileGUID[1,2,3]

string

this holds the GUID of the GML tree that the lValue1,2 or 3 refers to.





The event object holds the following properties:


Name

Type
Description



event

string
This is the event string



device
number

This is the event device



modifier

number
This holds the key modifier (0,1,2)



payloads

table of strings

This holds the payloads that came with this action.






Name

Type
Description



actionType

number

this is equal to the plugin id



actionSubType

number

this is the id of this particular action, choose freely



action

Action Object

this holds the actual action parameters

Yoggi
June 6th, 2015, 05:17 AM
Hi Ron,

I hope you'll find time to work on the plugin creation document as I think this is what’s needed for “new” girder users like me self.

You write “The goal of this document is to get started with a simple plugin that exposes the functionality of a PIO-1 or Global Cache like device.” Will this documentation (when finished) explain all the code need for one or the other, or more in general how they work?

I find my self scavenging for bits of needed information in the forum and Girder 6 help files (time consuming and hard for a newcomer) maybe the pugin learning could be “fast tracked” by writing one plugin from scratch with all the required steps and accompanying explanations.

This could/would eliminate the confusion I am experiencing with explanations that are to general and/or from different code examples.

What I am attempting is to write a plugin for a Sharp Aquos TV that has serial and tcp/ip control capability so this might be a good candidate but a Dennon/Marantz receiver might be an even better one (since more automation geek's own these)!

Examples of Sharp control codes and replies:

'MUTE? ' returns '1\r' for mute on and '2\r' for mute off
'CHUP ' returns 'OK\r' (Channel-up)

Control codes are always 8 characters (padded with blank spaces when needed).

I have the TV but not the Marantz receiver. My TV is currently packed up as I am doing a full renovation of my apartment (but the TVs responses are easily emulated).

Let me know if I can help out in any way!

Joachim