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):
void setup() {
}
void loop() {
}
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:
- at every defined time interval
- at a prefixed time of day
- 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:
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.
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:
|
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.
...
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";
?>