FANDOM


Automagically PluginsEdit

Automagically is built to be expendable through it's plugin system.
If you want to add new functionality, simply write your own plugin and share it with the community.

The template and the final result can be downloaded from Telldus Automagically forum

Plugins explainedEdit

A plugin is a python file located in the ~/source/automagically/site/plugins/ directory. It comprises three functions, the init(), the signalHandler() and the threadFunc()

The init functionEdit

Automagically will start the plugin by calling it's init function that is expected to respond according to this format: (SIGNALS, SETTINGS, SIGNALHANDLER, THREADHANDLER)

  • SIGNALS are the signal(s) that the plugin is registered to listen to
  • SETTINGS are the settings used by the plugin
  • SIGNALHANDLER is the function that handles incoming signals
  • THREADHANDLER is the function that runs the plugins main thread
def init():
    settings = {PLUGIN_ENABLED:('boolean', False)}
    return (PLUGIN_NAME, settings, signalHandler, threadFunc)

The signal handlerEdit

The signal handler should take a single argument, a string containing the signal.
It is recommended that the signal handler acts on the signal 'terminate' and gracefully shuts down.

def signalHandler(signal):
    print ' Received signal:' + signal.content.strip()
    if signal.content == 'terminate':
        workQueue.put(None)

The thread handlerEdit

The thread handler does not take any arguments but is expected to terminate when signal 'terminate' has been received by the signal handler.

def threadFunc():
    keepRunning = True
    while(keepRunning):
        s = workQueue.get(True) # Wait for a signal
        if s == None: # Time to close down
            workQueue.task_done()
            keepRunning = False
            continue

Plugin templateEdit

Below is a template plugin that is a good base to build your own plugin from

# Imports
from django.db import models
from settings.models import getSettings
import signals.models
import Queue

# Plugin configurations
PLUGIN_NAME = 'template'
PLUGIN_ENABLED =  'Enabled'

# Global variables
debug = False
workQueue = Queue.Queue()

# Signal handler
def signalHandler(signal):
    if debug:
        print PLUGIN_NAME + ' received signal:' + signal.content.strip()
    if signal.content == 'terminate':
        workQueue.put(None)
    elif signal.content.strip() == PLUGIN_NAME + ',configuration,changed':
        workQueue.put('update')
    elif signal.content.startswith(PLUGIN_NAME + ',action:'): # Note: Change to your needs
        # Received an 'action' signal
        for i in signal.content.strip()[16:].split(';'):      # Note: 16 = len("template,action:"), change to match your plugin name and needs
            if i != :
                workQueue.put(i)

# Thread function
def threadFunc():
    currentSettings = {}
    keepRunning = True
    nextUpdate = 60 #seconds

    # Load plugin settings
    currentSettings = getSettings(PLUGIN_NAME)
    pluginEnabled = currentSettings[PLUGIN_ENABLED]
    
    # Main loop
    while(keepRunning):
        try:
            try:
                if pluginEnabled and nextUpdate > 0:
                    s = workQueue.get(True, nextUpdate) # Wait for a signal or a timeout
                else:
                    s = workQueue.get(True) # Wait for a signal
                
            except:
                # Exception occurs when nextUpdate time has elapsed
                s = 'TIMEOUT'
                if debug:
                    print PLUGIN_NAME + ' time to act'

            if s == None: # Time to close down
                if debug:
                    print PLUGIN_NAME + ' time to close down'
                workQueue.task_done()
                keepRunning = False
                continue
                
            elif s == 'update': # Configuration has changed, update to new values
                if debug:
                    print PLUGIN_NAME + ' settings updated'
                currentSettings = getSettings(PLUGIN_NAME)
                pluginEnabled = currentSettings[PLUGIN_ENABLED]
                
                ##
                ## CODE HERE TO ACT WHEN CONFIGURATION CHANGES OCCURS
                ##
                
                workQueue.task_done()
                continue
    
            elif s == 'TIMEOUT':
                if debug:
                    print PLUGIN_NAME + ' acting on timeout'
                if pluginEnabled:
                    ##
                    ## CODE HERE TO ACT ON REGULAR INTERVAL
                    ##
                    print "Acting on interval for plugin " + PLUGIN_NAME
                
                # Note: This part should NOT have a workQueue.task_done()
                continue
                
            else: # A signal was received
                if debug:
                    print PLUGIN_NAME + ' got signal:' + s
                if pluginEnabled:
                    ##
                    ## CODE HERE TO ACT WHEN A SIGNAL IS RECEIVED
                    ##
                    print "Acting on signal for plugin " + PLUGIN_NAME                        
                    
                workQueue.task_done()
                continue

        except:
            print 'Error in ' + PLUGIN_NAME + ' threadfunction'
            if debug:
                raise

def init():
    settings = {PLUGIN_ENABLED:('boolean', False)}
    return (PLUGIN_NAME, settings, signalHandler, threadFunc)

Plugin tutorial
Edit

In this tutorial we'll write a plugin that fetches weather information from the Swedish weather institute, SMHI.
It is based on the template above.


Step #1 - Name your plugin and associated changesEdit

Line 8, replace

PLUGIN_NAME = 'template'

with

PLUGIN_NAME = 'smhi'


Line 25, replace

for i in signal.content.strip()[16:].split(';'):      # Note: 16 = len("template,action:"), change to match your plugin name and needs

with

for i in signal.content.strip()[12:].split(';'):      # Note: 12 = len("smhi,action:")


Step #2 - Add additional settings if needed
Edit

This plugin shall have a configurable interval, i.e. how often it fetches data, and a configuration on how many datapoints that shall be fetched.

In Plugin configurations, add

PLUGIN_INTERVAL = 'Interval'
PLUGIN_DATAPOINTS = 'Datapoints'


In init function, change settings code to

settings = {PLUGIN_ENABLED:     ('boolean', False),
            PLUGIN_DATAPOINTS:  ('integer', 3),
            PLUGIN_INTERVAL:    ('integer', 60)}


In the threadFunction, add this (both in the # Load plugin settings and in the elif s == 'update section)

pluginDatapoints = currentSettings[PLUGIN_DATAPOINTS]
nextUpdate = currentSettings[PLUGIN_INTERVAL]


You can now remove this line

nextUpdate = 60 #seconds


Step #3 - This plugin uses the general settings Longitude and Latitude
Edit

Add imports related to handling general settings

from general import getSetting


Make the plugin listen to general signals in init function.
Change from

return (PLUGIN_NAME, settings, signalHandler, threadFunc)

to

return ([PLUGIN_NAME, 'general'], settings, signalHandler, threadFunc)


Make the threadFunction read the settings at startup, add this before the main loop

# Load settings
lat=str(getSetting('Location Latitude'))
lon=str(getSetting('Location Longitude'))


Add a signal handler that handles the general setting in the signalHandler

elif signal.content.strip() == 'general,configuration,changed':
    workQueue.put('generalupdate')


Make the threadFunction act on a general update by adding

elif s == 'generalupdate': # Configuration has changed, update to new values
    if debug:
        print 'General settings updated, affecting ' + PLUGIN_NAME
    lat=str(getSetting('Location Latitude'))
    lon=str(getSetting('Location Longitude'))
    workQueue.task_done()
    continue

             
Step #4 - Add actions to be executed on regular interval
Edit

Add imports related to main functionality

import urllib2, json
from time import strftime, localtime
from datetime import datetime, timedelta


Add the main function

def smhi_parse(lat, lon, datapoints):
    url='http://opendata-download-metfcst.smhi.se/api/category/pmp1g/version/1/geopoint/lat/'+str(lat)+'/lon/'+str(lon)+'/data.json'
    if debug:
        print url 
    try:
        j = urllib2.urlopen(url)
        j_obj = json.load(j)
    except:
        print 'While fetching data from SMHI with' + PLUGIN_NAME + ', the plugin threw an exception'   

    output = PLUGIN_NAME + ',temperature'
    for predition_time in j_obj['timeseries']:
        for i in range(0, int(float(datapoints))):
            if predition_time['validTime'] == (datetime.now() + timedelta(hours=i)).strftime("%Y-%m-%dT%H:00:00Z"):
                output += ","+ str(predition_time['t'])

    signals.models.postToQueue(output, PLUGIN_NAME)


Replace this template code

##
## CODE HERE TO ACT ON REGULAR INTERVAL
##
print "Acting on interval for plugin " + PLUGIN_NAME

with

smhi_parse(lat, lon, pluginDatapoints)

Step #5 - Add actions to be executed when signal is receivedEdit

Replace this template code

##
## CODE HERE TO ACT WHEN A SIGNAL IS RECEIVED
##
print "Acting on signal for plugin " + PLUGIN_NAME

with

# Expecting: "fetch,N" where N is number of datapoints to fetch, e.g "smhi,action:fetch,4"
_s=s.split(',')
if len(_s) == 2:
    if _s[0] == 'fetch':
        smhi_parse(lat, lon, _s[1])

The final resultEdit

The final result is to saved in a file called smhi.py and copied to ~/source/automagically/site/plugins/.

$ sudo service automagically stop
$ cp smhi.py ~/source/automagically/site/plugins/.
$ sudo service automagically start

It should look something like this

# Imports
from django.db import models
from settings.models import getSettings
from general import getSetting
import signals.models
import Queue
import urllib2, json
from time import strftime, localtime
from datetime import datetime, timedelta

# Plugin configurations
PLUGIN_NAME = 'smhi'
PLUGIN_ENABLED =  'Enabled'
PLUGIN_INTERVAL = 'Interval'
PLUGIN_DATAPOINTS = 'Datapoints'


# Global variables
debug = False
workQueue = Queue.Queue()

def smhi_parse(lat, lon, datapoints):
    url='http://opendata-download-metfcst.smhi.se/api/category/pmp1g/version/1/geopoint/lat/'+str(lat)+'/lon/'+str(lon)+'/data.json'
    if debug:
        print url  
    try:
        j = urllib2.urlopen(url)
        j_obj = json.load(j)
    except:
        print 'While fetching data from SMHI with' + PLUGIN_NAME + ', the plugin threw an exception'
    
    output = PLUGIN_NAME + ',temperature'
    for predition_time in j_obj['timeseries']:
        for i in range(0, int(float(datapoints))):
            if predition_time['validTime'] == (datetime.now() + timedelta(hours=i)).strftime("%Y-%m-%dT%H:00:00Z"):
                output += ","+ str(predition_time['t'])

    signals.models.postToQueue(output, PLUGIN_NAME)


# Signal handler
def signalHandler(signal):
    if debug:
        print PLUGIN_NAME + ' received signal:' + signal.content.strip()
    if signal.content == 'terminate':
        workQueue.put(None)
    elif signal.content.strip() == 'general,configuration,changed':
        workQueue.put('generalupdate')
    elif signal.content.strip() == PLUGIN_NAME + ',configuration,changed':
        workQueue.put('update')
    elif signal.content.startswith(PLUGIN_NAME + ',action:'): # Note: Change to your needs
        # Received an 'action' signal
        for i in signal.content.strip()[12:].split(';'):
            if i != :
                workQueue.put(i)

# Thread function
def threadFunc():
    currentSettings = {}
    keepRunning = True

    # Load plugin settings
    currentSettings = getSettings(PLUGIN_NAME)
    pluginEnabled = currentSettings[PLUGIN_ENABLED]
    pluginDatapoints = currentSettings[PLUGIN_DATAPOINTS]
    nextUpdate = currentSettings[PLUGIN_INTERVAL]

    # Load settings
    lat=str(getSetting('Location Latitude'))
    lon=str(getSetting('Location Longitude'))
    
    # Main loop
    while(keepRunning):
        try:
            try:
                if pluginEnabled and nextUpdate > 0:
                    s = workQueue.get(True, nextUpdate) # Wait for a signal or a timeout
                else:
                    s = workQueue.get(True) # Wait for a signal
                
            except:
                # Exception occurs when nextUpdate time has elapsed
                s = 'TIMEOUT'
                if debug:
                    print PLUGIN_NAME + ' time to act'

            if s == None: # Time to close down
                if debug:
                    print PLUGIN_NAME + ' time to close down'
                workQueue.task_done()
                keepRunning = False
                continue
                
            elif s == 'generalupdate': # Configuration has changed, update to new values
                if debug:
                    print 'General settings updated, affecting ' + PLUGIN_NAME
                lat=str(getSetting('Location Latitude'))
                lon=str(getSetting('Location Longitude'))
                workQueue.task_done()
                continue

            elif s == 'update': # Configuration has changed, update to new values
                if debug:
                    print PLUGIN_NAME + ' settings updated'
                currentSettings = getSettings(PLUGIN_NAME)
                pluginEnabled = currentSettings[PLUGIN_ENABLED]
                pluginDatapoints = currentSettings[PLUGIN_DATAPOINTS]
                nextUpdate = currentSettings[PLUGIN_INTERVAL]
                
                ##
                ## CODE HERE TO ACT WHEN CONFIGURATION CHANGES OCCURS
                ##
                
                workQueue.task_done()
                continue
    
            elif s == 'TIMEOUT':
                if debug:
                    print PLUGIN_NAME + ' acting on timeout'
                if pluginEnabled:
                    smhi_parse(lat, lon, pluginDatapoints)
                
                # Note: This part should NOT have a workQueue.task_done()
                continue
                
            else: # A signal was received
                if debug:
                    print PLUGIN_NAME + ' got signal:' + s
                if pluginEnabled:
                    # Expecting: "fetch,N" where N is number of datapoints to fetch, e.g "smhi,action:fetch,4"
                    _s=s.split(',')
                    if len(_s) == 2:
                        if _s[0] == 'fetch':
                            smhi_parse(lat, lon, _s[1])

                workQueue.task_done()
                continue

        except:
            print 'Error in ' + PLUGIN_NAME + ' threadfunction'
            if debug:
                raise

def init():
    settings = {PLUGIN_ENABLED:     ('boolean', False),
                PLUGIN_DATAPOINTS:  ('integer', 3),
                PLUGIN_INTERVAL:    ('integer', 60)}
    return ([PLUGIN_NAME, 'general'], settings, signalHandler, threadFunc)
Community content is available under CC-BY-SA unless otherwise noted.