Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Raspberry-Pi

Python Handle Events for Arduino and Raspberry Cards

0.00/5 (No votes)
21 Apr 2014CPOL4 min read 12.9K   76  
A function for works on event

Image 1

Introduction

This tip is the result of a work with Arduino and Raspberry cards for controlling an experimental fish factory integrated with a hydroponic cultivation, developed by SERMIG in order to promote poor people emancipation.

A characteristic use of this type of card is to control sensors and/or to activate actions by a software which loops indefinitely; below is the skeleton of an Arduino script (called, in the Arduino jargon, sketch):

C++
void setup() {
  // put your setup code here, to run once:
}
void loop() {
  // put your main code here, to run repeatedly:
}

The tip illustrates a Python class, compatible both with Arduino and Raspberry, for schedule activities: the main loop contains a call to the scheduler and the developer must only describe the events and write the functions for handling them.

Background

The reader must have some knowledge on how to organize a program running in the automation cards, the language used and the concepts of event with associated action. Events can occur in many ways, where the most significant are:

  1. at every defined time interval
  2. at a prefixed time of day
  3. on command

Other possibilities, all easily achievable as variants of those listed above, are at the beginning or after a period of time and for a certain period starting from a given time.

Using the Code

The program is based on event a Python class which is used for creating event objects and which contains the scheduler and others functions; the schema for import and use of the class is:

C#
from cron import *
from time import sleep
# -------- event declaration -----------------------------------------
...
# -------- functions for handle events -------------------------------
...
def main(): 
    while True:
        sleep(event.croner())     # iterate cron
if __name__ == '__main__':
    main()

The function event.croner() is the scheduler, which returns a time used to put the microprocessor on wait. The cron module contains a class named event which, when instantiated, creates an object with the data necessary for handling the event:

  • every: Interval between two consecutive occurrence of this event
  • nextEvent: The time for the next occurrence of this event, it is decremented by the time elapsed from the precedent call of croner; when it falls to or under 0 the action associated is called
  • duration: The event duration, if greater than 0 the function which handles this event is called also after the duration time is expired, this is for controlling the start and the end of an action
  • status: The event status
  • action: The function which handles the event
  • name: The event name

The object event is created by passing the data in the order above or by name=value; by default every 60 seconds, nextEvent and duration are 0 seconds, the status is EVENT_START, i.e., enabled.

There are some semantic flavors for managing the times, which must be indicated in seconds or in the form 'hh:mm:ss' (minute and seconds can be omitted); internally times are stored in millisecond, besides for the field every we can also use 'day' instead of 86400 for indicating an event which occurs every day at a determinate time, this time is indicated in the field nextEvent; 'disabled' and 'manual' are symbolic values for status field meaning that the event is disabled (an event disabled, can be activated by program, an event declared manual when enabled, is automatically disabled after his occurrence).

Below are some examples of events.

C#
event('0:2:30',20,0,1, handle_t1,"t1")                          # every 150 seconds but after 20 seconds from program start
event(status='manual',action=handle_reboot,name='reboot') 
event('Day','12','0:1:30',action=handle_pump,name="pump")    # start at 12 o'clock, the event ends after 90 seconds

The class event has declared some useful constants and functions, for managing events, here is a list:

event.EVENT_DISABLED  
event.EVENT_START  
event.EVENT_END  
event.actionsList List of all actions
event.delay  
event.event(eventName) A function which returns the object
event.handle(event or eventName,action) To simplify some action:
  • Start
  • Stop
  • Halt
  • Resume
event.croner The event handler

How It Works

The scheduler decrements the nextEvent field of enabled events, by the time elapsed from preceding cycle, when nextEvent is 0 or less, the associated function is called. After the nextEvent is restored, the scheduler returns a decimal a value (0.5) which can be used to delay the processor.

Other Events Type

The events which occur on starting can be declared normally, and the event must be disabled in the function which handles this. See the example:

def atStart(cron):
    cron.status = event.EVENT_DISABLED    # or  event.handle(cron,'halt')
    print "Raspberry started"
...
event(action=atStart,nextEvent=25,name="houseKeeping")    # 25 seconds after start
...

Another possible action is a repetitive action which starts at a prefixed time, for example start an action every 3 minutes for 2 minutes in the period from eight o'clock to twenty o'clock.

...
event(every='0:3',duration='0:2',action=handle_pump,name="pump",status='disabled')
event('Day','8:00:00',duration='12',action=activate_pump,name="ActivatePump")
...
def handle_pump(cron):
    print("{2} name: {0!s} {1}".format(cron.name,("start" if cron.status == 
    event.EVENT_START else "stop"),event.datetime.now().strftime("%H:%M:%S")))
    if cron.status == event.EVENT_START: ...    # start pump
    else:  ...    # stop pump
def activate_pump(cron):
    print("{2} name: {0!s} {1}".format(cron.name,("start" if cron.status == 
    event.EVENT_START else "stop"),event.datetime.now().strftime("%H:%M:%S")))
    if cron.status == event.EVENT_START: event.event("pump").status = event.EVENT_START
    else: event.event("pump").status = event.EVENT_DISABLED
...

Tips

If the time of the microprocessor is not maintained, the program must provide the correct time in order to synchronize the daily events.
For avoiding the overlapping events, they can be started delayed, i.e., with different value in the nextEvent field.

C#
...
event(nextEvent=0,action=handle_temp,name="t1")        # the every is 60' by default
event(nextEvent=2,action=handle_temp,name="t2")         # this happens 2 seconds after t1 event
event(nextEvent=5,action=handle_http,name="http")    # this happens 5 seconds after t1 event
... 

The actions can interfere with the scheduler, especially if they are of a long duration, for example a communication via WEB; a possible workaround is to start the function as thread and to handle it asynchronously.

The example below shows a request made at WEB server which sends commands to start some events.

import time
from cron import *
import threading
from time import sleep
import httplib
import os
n = 0
def logger(cron):
    log = "{1} name: {0!s}".format(cron.name,event.datetime.now().strftime("%H:%M:%S"))
    log += " manual" if cron.manual == 1 else " every {0:.0f} seconds ".format(cron.every/1000)
    log += " for {0:.0f} seconds".format(cron.duration/1000) if cron.duration > 0 else ""
    log += " status {0:.0f}".format(cron.status)
    print log
def getCommand(cron):
    global n
    n += 1
    logger(cron)
    t = threading.Thread(target=sender, args=('127.0.0.1','condor/arduino/trycron.php','n='+str(n)))
    t.start()
def sender(url,script,data):
    conn = httplib.HTTPConnection(url)
    conn.request("GET", "/"+script+"?"+data)
    r1 = conn.getresponse()
    answer = r1.read()
    print (r1.status, r1.reason,answer)
    command = answer.split(",")
    if answer.find("Enable") >= 0:
        evnt = event.event(command[0])
        evnt.nextEvent = eval(command[1])*1000
        evnt.duration = eval(command[2])*1000
        evnt.status = event.EVENT_START
    if answer.find("Disable") >= 0:
        event.handle(command[0],'stop')  # force stop
    if answer.find("Close") >= 0:
        event.handle('close','start')
def handle_close(cron):
    logger(cron)
    time.sleep(5)
    exit(17)
def handle_pump(cron):
    logger(cron)
event(every=10,action=getCommand,name="webRequest")           # call site every 30''
event(action=handle_close,name="close",status='manual')       # this event is disabled and can be ativate by program
event(action=handle_pump,name="pump",status='manual')         # this event is disabled and can be ativate by program
while True:
    time.sleep(event.croner())

The web script is very rudimentary:

<?PHP
$count = $_REQUEST['n'];
$message = "";
if ($count == 7) $message = 'Close,';
else if ($count == 3)  $message = 'pump,5,90,Enable,';
else if ($count == 5)  $message = 'pump,Disable,';
echo $message."n=$count";
?>

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)