Add cron functionality
This commit is contained in:
parent
c6165a3bcf
commit
6b40e88ecb
7 changed files with 866 additions and 15 deletions
|
@ -1 +1,2 @@
|
||||||
time_src = time.c resource_gmtime.c resource_timestamp.c resource_timezone.c
|
time_src = time.c resource_gmtime.c resource_timestamp.c \
|
||||||
|
resource_timezone.c resource_crontab.c cron.c
|
||||||
|
|
118
apps/time/bitstring.h
Normal file
118
apps/time/bitstring.h
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 1989 The Regents of the University of California.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This code is derived from software contributed to Berkeley by
|
||||||
|
* Paul Vixie.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms are permitted
|
||||||
|
* provided that the above copyright notice and this paragraph are
|
||||||
|
* duplicated in all such forms and that any documentation,
|
||||||
|
* advertising materials, and other materials related to such
|
||||||
|
* distribution and use acknowledge that the software was developed
|
||||||
|
* by the University of California, Berkeley. The name of the
|
||||||
|
* University may not be used to endorse or promote products derived
|
||||||
|
* from this software without specific prior written permission.
|
||||||
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
*
|
||||||
|
* @(#)bitstring.h 5.2 (Berkeley) 4/4/90
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef unsigned char bitstr_t;
|
||||||
|
|
||||||
|
/* internal macros */
|
||||||
|
/* byte of the bitstring bit is in */
|
||||||
|
#define _bit_byte(bit) \
|
||||||
|
((bit) >> 3)
|
||||||
|
|
||||||
|
/* mask for the bit within its byte */
|
||||||
|
#define _bit_mask(bit) \
|
||||||
|
(1 << ((bit)&0x7))
|
||||||
|
|
||||||
|
/* external macros */
|
||||||
|
/* bytes in a bitstring of nbits bits */
|
||||||
|
#define bitstr_size(nbits) \
|
||||||
|
((((nbits) - 1) >> 3) + 1)
|
||||||
|
|
||||||
|
|
||||||
|
/* allocate a bitstring on the stack */
|
||||||
|
#define bit_decl(name, nbits) \
|
||||||
|
(name)[bitstr_size(nbits)]
|
||||||
|
|
||||||
|
/* is bit N of bitstring name set? */
|
||||||
|
#define bit_test(name, bit) \
|
||||||
|
((name)[_bit_byte(bit)] & _bit_mask(bit))
|
||||||
|
|
||||||
|
/* set bit N of bitstring name */
|
||||||
|
#define bit_set(name, bit) \
|
||||||
|
(name)[_bit_byte(bit)] |= _bit_mask(bit)
|
||||||
|
|
||||||
|
/* clear bit N of bitstring name */
|
||||||
|
#define bit_clear(name, bit) \
|
||||||
|
(name)[_bit_byte(bit)] &= ~_bit_mask(bit)
|
||||||
|
|
||||||
|
/* clear bits start ... stop in bitstring */
|
||||||
|
#define bit_nclear(name, start, stop) { \
|
||||||
|
register bitstr_t *_name = name; \
|
||||||
|
register int _start = start, _stop = stop; \
|
||||||
|
register int _startbyte = _bit_byte(_start); \
|
||||||
|
register int _stopbyte = _bit_byte(_stop); \
|
||||||
|
if (_startbyte == _stopbyte) { \
|
||||||
|
_name[_startbyte] &= ((0xff >> (8 - (_start&0x7))) | \
|
||||||
|
(0xff << ((_stop&0x7) + 1))); \
|
||||||
|
} else { \
|
||||||
|
_name[_startbyte] &= 0xff >> (8 - (_start&0x7)); \
|
||||||
|
while (++_startbyte < _stopbyte) \
|
||||||
|
_name[_startbyte] = 0; \
|
||||||
|
_name[_stopbyte] &= 0xff << ((_stop&0x7) + 1); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set bits start ... stop in bitstring */
|
||||||
|
#define bit_nset(name, start, stop) { \
|
||||||
|
register bitstr_t *_name = name; \
|
||||||
|
register int _start = start, _stop = stop; \
|
||||||
|
register int _startbyte = _bit_byte(_start); \
|
||||||
|
register int _stopbyte = _bit_byte(_stop); \
|
||||||
|
if (_startbyte == _stopbyte) { \
|
||||||
|
_name[_startbyte] |= ((0xff << (_start&0x7)) & \
|
||||||
|
(0xff >> (7 - (_stop&0x7)))); \
|
||||||
|
} else { \
|
||||||
|
_name[_startbyte] |= 0xff << ((_start)&0x7); \
|
||||||
|
while (++_startbyte < _stopbyte) \
|
||||||
|
_name[_startbyte] = 0xff; \
|
||||||
|
_name[_stopbyte] |= 0xff >> (7 - (_stop&0x7)); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* find first bit clear in name */
|
||||||
|
#define bit_ffc(name, nbits, value) { \
|
||||||
|
register bitstr_t *_name = name; \
|
||||||
|
register int _byte, _nbits = nbits; \
|
||||||
|
register int _stopbyte = _bit_byte(_nbits), _value = -1; \
|
||||||
|
for (_byte = 0; _byte <= _stopbyte; ++_byte) \
|
||||||
|
if (_name[_byte] != 0xff) { \
|
||||||
|
_value = _byte << 3; \
|
||||||
|
for (_stopbyte = _name[_byte]; (_stopbyte&0x1); \
|
||||||
|
++_value, _stopbyte >>= 1); \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
*(value) = _value; \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* find first bit set in name */
|
||||||
|
#define bit_ffs(name, nbits, value) { \
|
||||||
|
register bitstr_t *_name = name; \
|
||||||
|
register int _byte, _nbits = nbits; \
|
||||||
|
register int _stopbyte = _bit_byte(_nbits), _value = -1; \
|
||||||
|
for (_byte = 0; _byte <= _stopbyte; ++_byte) \
|
||||||
|
if (_name[_byte]) { \
|
||||||
|
_value = _byte << 3; \
|
||||||
|
for (_stopbyte = _name[_byte]; !(_stopbyte&0x1); \
|
||||||
|
++_value, _stopbyte >>= 1); \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
*(value) = _value; \
|
||||||
|
}
|
488
apps/time/cron.c
Normal file
488
apps/time/cron.c
Normal file
|
@ -0,0 +1,488 @@
|
||||||
|
/**
|
||||||
|
* \file
|
||||||
|
* cron: Cron-like functionality
|
||||||
|
* \author
|
||||||
|
* Ralf Schlatterbeck <rsc@runtux.com>
|
||||||
|
*
|
||||||
|
* \brief cron-like command scheduler
|
||||||
|
*
|
||||||
|
* Inspired by vixie's cron by Paul Vixie which is
|
||||||
|
* Copyright 1988,1990,1993,1994 by Paul Vixie
|
||||||
|
*
|
||||||
|
* Distribute freely, except: don't remove my name from the source or
|
||||||
|
* documentation (don't take credit for my work), mark your changes (don't
|
||||||
|
* get me blamed for your possible bugs), don't alter or remove this
|
||||||
|
* notice. May be sold if buildable source is provided to buyer. No
|
||||||
|
* warrantee of any kind, express or implied, is included with this
|
||||||
|
* software; use at your own risk, responsibility for damages (if any) to
|
||||||
|
* anyone resulting from the use of this software rests entirely with the
|
||||||
|
* user.
|
||||||
|
*
|
||||||
|
* Changes to make this work on a microcontroller by Ralf Schlatterbeck
|
||||||
|
* In fact this is mostly a rewrite but keeps central algorithms and
|
||||||
|
* some data structures of the original.
|
||||||
|
* The syntax is simplified, we don't support the @ notation (e.g.
|
||||||
|
* @hourly) and named entities (e.g. weekday names instead of numbers).
|
||||||
|
* Furthermore we can't send email and don't support environment
|
||||||
|
* variables. The called commands are functions registered via a
|
||||||
|
* registration mechanism before runtime.
|
||||||
|
* Copyright 2016 Ralf Schlatterbeck, distribute with the conditions
|
||||||
|
* given above.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "contiki.h"
|
||||||
|
#include "cron.h"
|
||||||
|
#include "time.h"
|
||||||
|
|
||||||
|
#undef DEBUG
|
||||||
|
|
||||||
|
static struct cron_entry cron_entries [MAX_CRON_ENTRIES];
|
||||||
|
static struct cron_cmd cron_commands [MAX_CRON_COMMANDS];
|
||||||
|
static size_t cron_registered_entries = 0;
|
||||||
|
static size_t cron_registered_commands = 0;
|
||||||
|
static struct cron_entry *entry_freelist = NULL;
|
||||||
|
static struct cron_entry *entry_list = NULL;
|
||||||
|
|
||||||
|
typedef int64_t minute_t;
|
||||||
|
static time_t start_time;
|
||||||
|
static minute_t cron_clock_time;
|
||||||
|
|
||||||
|
/* Register a new cron command
|
||||||
|
* A command consists of a name (which must be unique, uniqueness is not
|
||||||
|
* enforced currently as registrations are done once at initialization
|
||||||
|
* time), a function to be called and a parameter.
|
||||||
|
*/
|
||||||
|
int cron_register_command
|
||||||
|
(const char *name, void (*function)(void *), void * parameter)
|
||||||
|
{
|
||||||
|
if (cron_registered_commands >= MAX_CRON_COMMANDS) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
cron_commands [cron_registered_commands].name = name;
|
||||||
|
cron_commands [cron_registered_commands].function = function;
|
||||||
|
cron_commands [cron_registered_commands].parameter = parameter;
|
||||||
|
cron_registered_commands++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate a new crontab entry.
|
||||||
|
* This allocates from the freelist and returns NULL if no more entries
|
||||||
|
* are available.
|
||||||
|
* Implementation note: If cron_registered_entries is 0 we first
|
||||||
|
* initialize the entry_freelist and the entry_list.
|
||||||
|
*/
|
||||||
|
struct cron_entry *allocate_cron_entry (void)
|
||||||
|
{
|
||||||
|
size_t n;
|
||||||
|
struct cron_entry *e;
|
||||||
|
if (cron_registered_entries == 0) {
|
||||||
|
entry_list = NULL;
|
||||||
|
entry_freelist = &cron_entries [0];
|
||||||
|
for (n=0; n < (MAX_CRON_ENTRIES - 1); n++) {
|
||||||
|
cron_entries [n].next = &cron_entries [n+1];
|
||||||
|
}
|
||||||
|
cron_entries [MAX_CRON_ENTRIES - 1].next = NULL;
|
||||||
|
}
|
||||||
|
if (cron_registered_entries >= MAX_CRON_ENTRIES) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
e = entry_freelist;
|
||||||
|
entry_freelist = e->next;
|
||||||
|
e->next = entry_list;
|
||||||
|
entry_list = e;
|
||||||
|
cron_registered_entries++;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_cron_entry (struct cron_entry *obsolete)
|
||||||
|
{
|
||||||
|
struct cron_entry *e;
|
||||||
|
assert (cron_registered_entries);
|
||||||
|
if (entry_list == obsolete) {
|
||||||
|
entry_list = obsolete->next;
|
||||||
|
obsolete->next = entry_freelist;
|
||||||
|
entry_freelist = obsolete;
|
||||||
|
cron_registered_entries--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (e=entry_list; e; e=e->next) {
|
||||||
|
if (e->next == obsolete) {
|
||||||
|
e->next = obsolete->next;
|
||||||
|
obsolete->next = entry_freelist;
|
||||||
|
entry_freelist = obsolete;
|
||||||
|
cron_registered_entries--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert (e != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct cron_entry *get_cron_entry (size_t idx)
|
||||||
|
{
|
||||||
|
if (idx >= MAX_CRON_ENTRIES) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return &cron_entries [idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the number in bits, return 0 on success -1 on error */
|
||||||
|
static int set_element (bitstr_t *bits, int low, int high, int n)
|
||||||
|
{
|
||||||
|
if (n < low || n > high) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
bit_set (bits, (n - low));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *get_number (int *np, int high, const char *s)
|
||||||
|
{
|
||||||
|
char *endptr;
|
||||||
|
long l = strtol (s, &endptr, 10);
|
||||||
|
if (endptr == s || errno == ERANGE || l > high) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*np = l;
|
||||||
|
return endptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *get_range (bitstr_t *bits, int low, int high, const char *s)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int n1, n2, n3;
|
||||||
|
|
||||||
|
if (*s == '*') {
|
||||||
|
/* '*' means "first-last" but can be modified by /step */
|
||||||
|
n1 = low;
|
||||||
|
n2 = high;
|
||||||
|
if (*++s == '\0') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s = get_number (&n1, high, s);
|
||||||
|
if (s == NULL || *s == '\0') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (*s != '-') {
|
||||||
|
if (set_element (bits, low, high, n1) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
} else {
|
||||||
|
if (*++s == '\0') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
s = get_number (&n2, high, s);
|
||||||
|
if (*s == '\0') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (*s == '/') {
|
||||||
|
if (*++s == '\0') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
s = get_number (&n3, high, s);
|
||||||
|
if (*s == '\0') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n3 = 1;
|
||||||
|
}
|
||||||
|
/* Explicit check for sane values */
|
||||||
|
if (n1 < low || n1 > high || n2 < low || n2 > high) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (i=n1; i<=n2; i+=n3) {
|
||||||
|
if (set_element (bits, low, high, i) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *get_list (bitstr_t *bits, int low, int high, const char *s)
|
||||||
|
{
|
||||||
|
int done = 0;
|
||||||
|
bit_nclear (bits, 0, (high - low + 1));
|
||||||
|
while (!done) {
|
||||||
|
s = get_range (bits, low, high, s);
|
||||||
|
if (NULL == s) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (*s == ',') {
|
||||||
|
s++;
|
||||||
|
} else {
|
||||||
|
done = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Skip white space */
|
||||||
|
while (s && isspace (*s)) {
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse a single crontab entry on a single line.
|
||||||
|
* We get the line via CoAP.
|
||||||
|
* Returns 0 if parsed successfully, -1 on error.
|
||||||
|
* On error the err pointer is set to the error message.
|
||||||
|
*/
|
||||||
|
int parse_crontab_line
|
||||||
|
(const char *line, struct cron_entry *e, const char **err)
|
||||||
|
{
|
||||||
|
size_t n;
|
||||||
|
const char *s = line;
|
||||||
|
size_t cmd_len = 0;
|
||||||
|
|
||||||
|
e->flags &= ~VALID;
|
||||||
|
if (*s == '*') {
|
||||||
|
e->flags |= MIN_STAR;
|
||||||
|
}
|
||||||
|
s = get_list (e->minute, FIRST_MINUTE, LAST_MINUTE, s);
|
||||||
|
if (NULL == s || *s == '\0') {
|
||||||
|
*err = "minute";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (*s == '*') {
|
||||||
|
e->flags |= HR_STAR;
|
||||||
|
}
|
||||||
|
s = get_list (e->hour, FIRST_HOUR, LAST_HOUR, s);
|
||||||
|
if (NULL == s || *s == '\0') {
|
||||||
|
*err = "hour";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (*s == '*') {
|
||||||
|
e->flags |= DOM_STAR;
|
||||||
|
}
|
||||||
|
s = get_list (e->dom, FIRST_DOM, LAST_DOM, s);
|
||||||
|
if (NULL == s || *s == '\0') {
|
||||||
|
*err = "dom";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s = get_list (e->month, FIRST_MONTH, LAST_MONTH, s);
|
||||||
|
if (NULL == s || *s == '\0') {
|
||||||
|
*err = "month";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (*s == '*') {
|
||||||
|
e->flags |= DOW_STAR;
|
||||||
|
}
|
||||||
|
s = get_list (e->dow, FIRST_DOW, LAST_DOW, s);
|
||||||
|
if (NULL == s || *s == '\0') {
|
||||||
|
*err = "dow";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* Make sundays equivalent */
|
||||||
|
if (bit_test (e->dow, 0) || bit_test (e->dow, 7)) {
|
||||||
|
bit_set (e->dow, 0);
|
||||||
|
bit_set (e->dow, 7);
|
||||||
|
}
|
||||||
|
/* strip whitespace at *end* of command
|
||||||
|
* by getting length without whitespace
|
||||||
|
*/
|
||||||
|
for (n=0; s [n]; n++) {
|
||||||
|
if (!isspace (s [n])) {
|
||||||
|
cmd_len = n + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (n=0; n<cron_registered_commands; n++) {
|
||||||
|
if ( cmd_len == strlen (cron_commands [n].name)
|
||||||
|
&& !strncmp (cron_commands [n].name, s, cmd_len)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
e->cmd = &cron_commands [n];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (n == cron_registered_commands) {
|
||||||
|
*err = "command";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
e->flags |= VALID;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_time (void)
|
||||||
|
{
|
||||||
|
struct tm *tm;
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday (&tv, NULL);
|
||||||
|
start_time = tv.tv_sec;
|
||||||
|
tm = localtime (&start_time);
|
||||||
|
/* We adjust the time to GMT so we can catch DST changes */
|
||||||
|
cron_clock_time = (start_time + tm->tm_gmtoff) / (time_t)SECONDS_PER_MINUTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void find_jobs (minute_t vtime, int do_wild, int do_nonwild)
|
||||||
|
{
|
||||||
|
time_t virtual_second = vtime * SECONDS_PER_MINUTE;
|
||||||
|
struct tm *tm = gmtime (&virtual_second);
|
||||||
|
int minute, hour, dom, month, dow;
|
||||||
|
struct cron_entry *e;
|
||||||
|
|
||||||
|
/* make 0-based values out of these so we can use them as indicies */
|
||||||
|
minute = tm->tm_min -FIRST_MINUTE;
|
||||||
|
hour = tm->tm_hour -FIRST_HOUR;
|
||||||
|
dom = tm->tm_mday -FIRST_DOM;
|
||||||
|
month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
|
||||||
|
dow = tm->tm_wday -FIRST_DOW;
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf ("%d %d %d %d %d\n", minute, hour, dom, month, dow);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
|
||||||
|
* first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
|
||||||
|
* on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this
|
||||||
|
* is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre.
|
||||||
|
* like many bizarre things, it's the standard.
|
||||||
|
*/
|
||||||
|
for (e = entry_list; e; e = e->next) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
if (e->flags & VALID) {
|
||||||
|
printf ("Checking entry %s\n", e->cmd->name);
|
||||||
|
}
|
||||||
|
printf ("valid: %d\n", (e->flags & VALID));
|
||||||
|
printf ("minute: %d\n", (bit_test (e->minute, minute)));
|
||||||
|
printf ("hour: %d\n", (bit_test (e->hour, hour)));
|
||||||
|
printf ("month %d\n", (bit_test (e->month, month)));
|
||||||
|
printf ("dom* %d\n", (e->flags & DOM_STAR));
|
||||||
|
printf ("dow* %d\n", (e->flags & DOW_STAR));
|
||||||
|
printf ("dow %d\n", (bit_test (e->dow, dow)));
|
||||||
|
printf ("dom %d\n", (bit_test (e->dom, dom)));
|
||||||
|
printf ("min* %d\n", (e->flags & MIN_STAR));
|
||||||
|
printf ("hr* %d\n", (e->flags & HR_STAR));
|
||||||
|
printf ("nonwild %d\n", (do_nonwild));
|
||||||
|
#endif
|
||||||
|
if ( (e->flags & VALID)
|
||||||
|
&& bit_test (e->minute, minute)
|
||||||
|
&& bit_test (e->hour, hour)
|
||||||
|
&& bit_test (e->month, month)
|
||||||
|
&& ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
|
||||||
|
? (bit_test (e->dow, dow) && bit_test (e->dom, dom))
|
||||||
|
: (bit_test (e->dow, dow) || bit_test (e->dom, dom))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if ( (do_nonwild && !(e->flags & (MIN_STAR | HR_STAR)))
|
||||||
|
|| (do_wild && (e->flags & (MIN_STAR | HR_STAR)))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
printf ("Cron: calling \"%s\"\n", e->cmd->name);
|
||||||
|
e->cmd->function (e->cmd->parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The cron callback function must be run regularly, at least once per
|
||||||
|
* minute.
|
||||||
|
* Implementation:
|
||||||
|
* Clocks are in minutes since the epoch (time() / 60).
|
||||||
|
* virtual_time is the time it *would* be if we are called promptly and
|
||||||
|
* nobody ever changed the clock. It is monotonically increasing...
|
||||||
|
* unless a timejump happens.
|
||||||
|
* time_running is the time we were last called. We determine
|
||||||
|
* initialization by checking time_running for -1.
|
||||||
|
* cron_clock_time is the current time for this run.
|
||||||
|
*/
|
||||||
|
void cron (void)
|
||||||
|
{
|
||||||
|
static minute_t time_running = -1;
|
||||||
|
static minute_t virtual_time = -1;
|
||||||
|
minute_t time_diff;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ... calculate how the current time differs from
|
||||||
|
* our virtual clock. Classify the change into one
|
||||||
|
* of 4 cases
|
||||||
|
*/
|
||||||
|
set_time ();
|
||||||
|
if (time_running == cron_clock_time) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf ("time not reached\n");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
time_running = cron_clock_time;
|
||||||
|
time_diff = time_running - virtual_time;
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf ("time_diff: %ld\n", (long)time_diff);
|
||||||
|
#endif
|
||||||
|
/* shortcut for the most common case */
|
||||||
|
if (time_diff == 1) {
|
||||||
|
virtual_time = time_running;
|
||||||
|
find_jobs (virtual_time, 1, 1);
|
||||||
|
} else {
|
||||||
|
int wakeup_kind = -1;
|
||||||
|
if (time_diff > -(3*MINUTE_COUNT)) {
|
||||||
|
wakeup_kind = 0;
|
||||||
|
}
|
||||||
|
if (time_diff > 0) {
|
||||||
|
wakeup_kind = 1;
|
||||||
|
}
|
||||||
|
if (time_diff > 5) {
|
||||||
|
wakeup_kind = 2;
|
||||||
|
}
|
||||||
|
if (time_diff > (3*MINUTE_COUNT)) {
|
||||||
|
wakeup_kind = 3;
|
||||||
|
}
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf ("wakeup_kind: %d\n", wakeup_kind);
|
||||||
|
#endif
|
||||||
|
switch (wakeup_kind) {
|
||||||
|
/* time_diff is a small positive number (wokeup late)
|
||||||
|
* run jobs for each virtual minute until caught up.
|
||||||
|
*/
|
||||||
|
case 1:
|
||||||
|
do {
|
||||||
|
virtual_time++;
|
||||||
|
find_jobs (virtual_time, 1, 1);
|
||||||
|
} while (virtual_time < time_running);
|
||||||
|
break;
|
||||||
|
/* time_diff is a medium-sized positive number, for example
|
||||||
|
* because we went to DST. Run wildcard jobs once, then run any
|
||||||
|
* fixed-time jobs that would otherwise be skipped. If we use up
|
||||||
|
* our minute (possible, if there are a lot of jobs to run) go
|
||||||
|
* around the loop again so that wildcard jobs have a chance to
|
||||||
|
* run, and we do our housekeeping.
|
||||||
|
*/
|
||||||
|
case 2:
|
||||||
|
/* run wildcard jobs for current minute */
|
||||||
|
find_jobs (time_running, 1, 0);
|
||||||
|
/* run fixed-time jobs for each minute missed */
|
||||||
|
do {
|
||||||
|
virtual_time++;
|
||||||
|
find_jobs (virtual_time, 0, 1);
|
||||||
|
set_time ();
|
||||||
|
} while ( virtual_time < time_running
|
||||||
|
&& cron_clock_time == time_running
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
/* time_diff is a small or medium-sized negative num, eg.
|
||||||
|
* because of DST ending. Just run the wildcard jobs. The
|
||||||
|
* fixed-time jobs probably have already run, and should not be
|
||||||
|
* repeated. virtual_time does not change until we are caught up.
|
||||||
|
*/
|
||||||
|
case 0:
|
||||||
|
find_jobs (time_running, 1, 0);
|
||||||
|
break;
|
||||||
|
/* Other: time has changed a *lot*, jump virtual time, and run
|
||||||
|
* everything
|
||||||
|
*/
|
||||||
|
default:
|
||||||
|
virtual_time = time_running;
|
||||||
|
find_jobs (time_running, 1, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
apps/time/cron.h
Normal file
91
apps/time/cron.h
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Definitions for cron
|
||||||
|
* Inspired by vixie's cron by Paul Vixie which is
|
||||||
|
* Copyright 1988,1990,1993,1994 by Paul Vixie
|
||||||
|
*
|
||||||
|
* Distribute freely, except: don't remove my name from the source or
|
||||||
|
* documentation (don't take credit for my work), mark your changes (don't
|
||||||
|
* get me blamed for your possible bugs), don't alter or remove this
|
||||||
|
* notice. May be sold if buildable source is provided to buyer. No
|
||||||
|
* warrantee of any kind, express or implied, is included with this
|
||||||
|
* software; use at your own risk, responsibility for damages (if any) to
|
||||||
|
* anyone resulting from the use of this software rests entirely with the
|
||||||
|
* user.
|
||||||
|
* Changes to make this work on a microcontroller by Ralf Schlatterbeck
|
||||||
|
* In fact this is mostly a rewrite but keeps central algorithms of the
|
||||||
|
* original.
|
||||||
|
* Copyright 2016 Ralf Schlatterbeck, distribute with the conditions
|
||||||
|
* given above.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _cron_h_
|
||||||
|
#define _cron_h_
|
||||||
|
|
||||||
|
#include "bitstring.h"
|
||||||
|
|
||||||
|
#define SECONDS_PER_MINUTE 60
|
||||||
|
|
||||||
|
#define FIRST_MINUTE 0
|
||||||
|
#define LAST_MINUTE 59
|
||||||
|
#define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1)
|
||||||
|
|
||||||
|
#define FIRST_HOUR 0
|
||||||
|
#define LAST_HOUR 23
|
||||||
|
#define HOUR_COUNT (LAST_HOUR - FIRST_HOUR + 1)
|
||||||
|
|
||||||
|
#define FIRST_DOM 1
|
||||||
|
#define LAST_DOM 31
|
||||||
|
#define DOM_COUNT (LAST_DOM - FIRST_DOM + 1)
|
||||||
|
|
||||||
|
#define FIRST_MONTH 1
|
||||||
|
#define LAST_MONTH 12
|
||||||
|
#define MONTH_COUNT (LAST_MONTH - FIRST_MONTH + 1)
|
||||||
|
|
||||||
|
/* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */
|
||||||
|
#define FIRST_DOW 0
|
||||||
|
#define LAST_DOW 7
|
||||||
|
#define DOW_COUNT (LAST_DOW - FIRST_DOW + 1)
|
||||||
|
|
||||||
|
#ifndef MAX_CRON_ENTRIES
|
||||||
|
#define MAX_CRON_ENTRIES 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAX_CRON_COMMANDS
|
||||||
|
#define MAX_CRON_COMMANDS 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct cron_cmd {
|
||||||
|
const char *name;
|
||||||
|
void (*function)(void *);
|
||||||
|
void *parameter;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cron_entry {
|
||||||
|
struct cron_entry *next;
|
||||||
|
struct cron_cmd *cmd;
|
||||||
|
bitstr_t bit_decl(minute, MINUTE_COUNT);
|
||||||
|
bitstr_t bit_decl(hour, HOUR_COUNT);
|
||||||
|
bitstr_t bit_decl(dom, DOM_COUNT);
|
||||||
|
bitstr_t bit_decl(month, MONTH_COUNT);
|
||||||
|
bitstr_t bit_decl(dow, DOW_COUNT);
|
||||||
|
int flags;
|
||||||
|
|
||||||
|
#define DOM_STAR 0x01
|
||||||
|
#define DOW_STAR 0x02
|
||||||
|
#define WHEN_REBOOT 0x04
|
||||||
|
#define MIN_STAR 0x08
|
||||||
|
#define HR_STAR 0x10
|
||||||
|
#define VALID 0x20
|
||||||
|
};
|
||||||
|
|
||||||
|
extern int parse_crontab_line
|
||||||
|
(const char *line, struct cron_entry *e, const char **err);
|
||||||
|
extern int cron_register_command
|
||||||
|
(const char *name, void (*function)(void *), void * parameter);
|
||||||
|
|
||||||
|
extern struct cron_entry *allocate_cron_entry (void);
|
||||||
|
extern struct cron_entry *get_cron_entry (size_t idx);
|
||||||
|
extern void free_cron_entry (struct cron_entry *e);
|
||||||
|
extern void cron (void);
|
||||||
|
|
||||||
|
#endif /* _cron_h_ */
|
118
apps/time/resource_crontab.c
Normal file
118
apps/time/resource_crontab.c
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
/**
|
||||||
|
* \file
|
||||||
|
* Resource for crontab entry
|
||||||
|
* \author
|
||||||
|
* Ralf Schlatterbeck <rsc@runtux.com>
|
||||||
|
*
|
||||||
|
* \brief get/put crontab entry
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "contiki.h"
|
||||||
|
#include "time_resource.h"
|
||||||
|
#include "jsonparse.h"
|
||||||
|
#include "er-coap.h"
|
||||||
|
#include "generic_resource.h"
|
||||||
|
#include "cron.h"
|
||||||
|
|
||||||
|
static size_t get_index_from_uri (const char *uri)
|
||||||
|
{
|
||||||
|
const char *s;
|
||||||
|
char *endptr;
|
||||||
|
size_t idx;
|
||||||
|
if (uri == NULL) {
|
||||||
|
return MAX_CRON_ENTRIES;
|
||||||
|
}
|
||||||
|
if (NULL == (s = strrchr (uri, '/'))) {
|
||||||
|
return MAX_CRON_ENTRIES;
|
||||||
|
}
|
||||||
|
idx = strtoul (s+1, &endptr, 10);
|
||||||
|
if (s == endptr || *endptr != '\0') {
|
||||||
|
return MAX_CRON_ENTRIES;
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crontab_from_string (const char *name, const char *uri, const char *s)
|
||||||
|
{
|
||||||
|
const char *err;
|
||||||
|
int res;
|
||||||
|
size_t idx = get_index_from_uri (uri);
|
||||||
|
if (idx >= MAX_CRON_ENTRIES) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
res = parse_crontab_line (s, get_cron_entry (idx), &err);
|
||||||
|
if (res < 0) {
|
||||||
|
printf ("Error parsing: %s\n", err);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
crontab_to_string (const char *name, const char *uri, char *buf, size_t bsize)
|
||||||
|
{
|
||||||
|
/* FIXME: For now we only return "valid" or "invalid" until someone
|
||||||
|
* comes up with a clever algorithm to reconstruct a crontab string
|
||||||
|
* from the cron_entry struct.
|
||||||
|
*/
|
||||||
|
size_t idx = get_index_from_uri (uri);
|
||||||
|
struct cron_entry *e;
|
||||||
|
if (idx >= MAX_CRON_ENTRIES) {
|
||||||
|
return MAX_GET_STRING_LENGTH;
|
||||||
|
}
|
||||||
|
e = get_cron_entry (idx);
|
||||||
|
if (e->flags & VALID) {
|
||||||
|
return snprintf (buf, bsize, "valid");
|
||||||
|
}
|
||||||
|
return snprintf (buf, bsize, "invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
GENERIC_RESOURCE
|
||||||
|
( crontab
|
||||||
|
, crontab-entry
|
||||||
|
, s
|
||||||
|
, 1
|
||||||
|
, crontab_from_string
|
||||||
|
, crontab_to_string
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Allocate all cron entries and the necessary resources */
|
||||||
|
void activate_cron_resources (void)
|
||||||
|
{
|
||||||
|
size_t n;
|
||||||
|
for (n=0; n<MAX_CRON_ENTRIES; n++) {
|
||||||
|
resource_t *res;
|
||||||
|
char *buf;
|
||||||
|
size_t len;
|
||||||
|
struct cron_entry *e;
|
||||||
|
char name [15];
|
||||||
|
/* Need to copy the resource because resource->url holds the path
|
||||||
|
* under which we activate it using rest_activate_resource
|
||||||
|
*/
|
||||||
|
if (NULL == (res = malloc (sizeof (*res)))) {
|
||||||
|
printf ("Error malloc\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
memcpy (res, &res_crontab, sizeof (*res));
|
||||||
|
e = allocate_cron_entry ();
|
||||||
|
assert (!(e->flags & VALID));
|
||||||
|
len = snprintf (name, sizeof (name), "crontab/%u", n);
|
||||||
|
name [sizeof (name) -1] = '\0';
|
||||||
|
assert (len < 15);
|
||||||
|
if (NULL == (buf = malloc (len + 1))) {
|
||||||
|
printf ("Error malloc\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
strcpy (buf, name);
|
||||||
|
rest_activate_resource (res, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* VI settings, see coding style
|
||||||
|
* ex:ts=8:et:sw=2
|
||||||
|
*/
|
||||||
|
|
|
@ -16,13 +16,17 @@
|
||||||
|
|
||||||
#ifndef time_resource_h
|
#ifndef time_resource_h
|
||||||
#define time_resource_h
|
#define time_resource_h
|
||||||
|
#include <assert.h>
|
||||||
#include "contiki.h"
|
#include "contiki.h"
|
||||||
#include "rest-engine.h"
|
#include "rest-engine.h"
|
||||||
|
|
||||||
extern resource_t res_timestamp;
|
extern resource_t res_timestamp;
|
||||||
extern resource_t res_timezone;
|
extern resource_t res_timezone;
|
||||||
|
extern resource_t res_crontab;
|
||||||
extern resource_t res_localtime;
|
extern resource_t res_localtime;
|
||||||
extern resource_t res_utc;
|
extern resource_t res_utc;
|
||||||
|
|
||||||
|
extern void activate_cron_resources (void);
|
||||||
|
|
||||||
#endif // time_resource_h
|
#endif // time_resource_h
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2011, Matthias Kovatsch and other contributors.
|
* Copyright (C) 2016, Ralf Schlatterbeck and other contributors.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
@ -44,8 +44,10 @@
|
||||||
#include "contiki-net.h"
|
#include "contiki-net.h"
|
||||||
#include "er-coap-engine.h"
|
#include "er-coap-engine.h"
|
||||||
#include "time.h"
|
#include "time.h"
|
||||||
|
#include "cron.h"
|
||||||
#include "time_resource.h"
|
#include "time_resource.h"
|
||||||
#include "jsonparse.h"
|
#include "jsonparse.h"
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
#if PLATFORM_HAS_BUTTON
|
#if PLATFORM_HAS_BUTTON
|
||||||
#include "dev/button-sensor.h"
|
#include "dev/button-sensor.h"
|
||||||
|
@ -82,7 +84,7 @@ extern resource_t res_radio;
|
||||||
//******************************************************************************/
|
//******************************************************************************/
|
||||||
|
|
||||||
void
|
void
|
||||||
hw_init()
|
hw_init ()
|
||||||
{
|
{
|
||||||
#if defined (PLATFORM_HAS_LEDS)
|
#if defined (PLATFORM_HAS_LEDS)
|
||||||
leds_off(LEDS_RED);
|
leds_off(LEDS_RED);
|
||||||
|
@ -93,8 +95,22 @@ PROCESS(rest_server_example, "Erbium Example Server");
|
||||||
|
|
||||||
AUTOSTART_PROCESSES(&rest_server_example, &sensors_process);
|
AUTOSTART_PROCESSES(&rest_server_example, &sensors_process);
|
||||||
|
|
||||||
|
#define LED_PIN 4
|
||||||
|
#define LOOP_INTERVAL (30 * CLOCK_SECOND)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set led to on or off, we abuse the given pointer to simply carry the
|
||||||
|
* on/off flag.
|
||||||
|
*/
|
||||||
|
void led_set (void *onoff)
|
||||||
|
{
|
||||||
|
int status = (int)onoff;
|
||||||
|
digitalWrite (LED_PIN, (status ? 0 : 1));
|
||||||
|
}
|
||||||
|
|
||||||
PROCESS_THREAD(rest_server_example, ev, data)
|
PROCESS_THREAD(rest_server_example, ev, data)
|
||||||
{
|
{
|
||||||
|
static struct etimer loop_periodic_timer;
|
||||||
PROCESS_BEGIN();
|
PROCESS_BEGIN();
|
||||||
PRINTF("Starting Erbium Example Server\n");
|
PRINTF("Starting Erbium Example Server\n");
|
||||||
|
|
||||||
|
@ -112,32 +128,47 @@ PROCESS_THREAD(rest_server_example, ev, data)
|
||||||
|
|
||||||
/* if static routes are used rather than RPL */
|
/* if static routes are used rather than RPL */
|
||||||
#if !UIP_CONF_IPV6_RPL && !defined (CONTIKI_TARGET_MINIMAL_NET) && !defined (CONTIKI_TARGET_NATIVE)
|
#if !UIP_CONF_IPV6_RPL && !defined (CONTIKI_TARGET_MINIMAL_NET) && !defined (CONTIKI_TARGET_NATIVE)
|
||||||
set_global_address();
|
set_global_address ();
|
||||||
configure_routing();
|
configure_routing ();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Initialize the OSD Hardware. */
|
/* Initialize the OSD Hardware. */
|
||||||
hw_init();
|
hw_init ();
|
||||||
/* Initialize the REST engine. */
|
/* Initialize the REST engine. */
|
||||||
rest_init_engine();
|
rest_init_engine ();
|
||||||
|
|
||||||
/* Activate the application-specific resources. */
|
/* Activate the application-specific resources. */
|
||||||
rest_activate_resource(&res_leds, "s/leds");
|
rest_activate_resource (&res_leds, "s/leds");
|
||||||
|
|
||||||
|
|
||||||
#if defined (PLATFORM_HAS_BATTERY) && REST_RES_BATTERY
|
#if defined (PLATFORM_HAS_BATTERY) && REST_RES_BATTERY
|
||||||
SENSORS_ACTIVATE(battery_sensor);
|
SENSORS_ACTIVATE(battery_sensor);
|
||||||
rest_activate_resource(&res_battery, "s/battery");
|
rest_activate_resource (&res_battery, "s/battery");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
rest_activate_resource(&res_timestamp, "clock/timestamp");
|
rest_activate_resource (&res_timestamp, "clock/timestamp");
|
||||||
rest_activate_resource(&res_timezone, "clock/timezone");
|
rest_activate_resource (&res_timezone, "clock/timezone");
|
||||||
rest_activate_resource(&res_localtime, "clock/localtime");
|
rest_activate_resource (&res_localtime, "clock/localtime");
|
||||||
rest_activate_resource(&res_utc, "clock/utc");
|
rest_activate_resource (&res_utc, "clock/utc");
|
||||||
|
|
||||||
/* Define application-specific events here. */
|
/* Register callback function(s) */
|
||||||
while(1) {
|
cron_register_command ("led_on", led_set, (void *)1);
|
||||||
|
cron_register_command ("led_off", led_set, (void *)0);
|
||||||
|
|
||||||
|
/* Allocate all cron entries and the necessary resources */
|
||||||
|
activate_cron_resources ();
|
||||||
|
|
||||||
|
/* Define application-specific events here.
|
||||||
|
* We need to call cron every 30 seconds or so (at least once a
|
||||||
|
* minute)
|
||||||
|
*/
|
||||||
|
etimer_set (&loop_periodic_timer, LOOP_INTERVAL);
|
||||||
|
while (1) {
|
||||||
PROCESS_WAIT_EVENT();
|
PROCESS_WAIT_EVENT();
|
||||||
|
if (etimer_expired (&loop_periodic_timer)) {
|
||||||
|
cron ();
|
||||||
|
etimer_reset (&loop_periodic_timer);
|
||||||
|
}
|
||||||
} /* while (1) */
|
} /* while (1) */
|
||||||
|
|
||||||
PROCESS_END();
|
PROCESS_END();
|
||||||
|
|
Loading…
Add table
Reference in a new issue