Add wallclock time handling
New application and new example. We use the built-in timer routines and add an offset to get the wallclock time. The offset can be set by time-changing routines (currently only settimeofday). We also maintain an offset for timezone handling but this isn't currently fully implemented.
This commit is contained in:
parent
be01bf77a9
commit
f48566d51f
11 changed files with 1046 additions and 0 deletions
1
apps/time/Makefile.time
Normal file
1
apps/time/Makefile.time
Normal file
|
@ -0,0 +1 @@
|
|||
time_src = time.c resource_gmtime.c resource_timestamp.c
|
126
apps/time/resource_gmtime.c
Normal file
126
apps/time/resource_gmtime.c
Normal file
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* \file
|
||||
* Resource for gmtime (utc) / localtime handling
|
||||
* \author
|
||||
* Ralf Schlatterbeck <rsc@runtux.com>
|
||||
*
|
||||
* \brief get time as a string in utc or localtime
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "contiki.h"
|
||||
#include "time.h"
|
||||
#include "time_resource.h"
|
||||
#include "jsonparse.h"
|
||||
/* Only coap 13 for now */
|
||||
#include "er-coap-13.h"
|
||||
|
||||
RESOURCE \
|
||||
( localtime
|
||||
, METHOD_GET
|
||||
, "clock/localtime"
|
||||
, "title=\"Time\";rt=\"localtime\""
|
||||
);
|
||||
|
||||
RESOURCE \
|
||||
( gmtime
|
||||
, METHOD_GET
|
||||
, "clock/utc"
|
||||
, "title=\"Time\";rt=\"utc\""
|
||||
);
|
||||
|
||||
void
|
||||
time_handler
|
||||
( void* request
|
||||
, void* response
|
||||
, uint8_t *buffer
|
||||
, uint16_t preferred_size
|
||||
, int32_t *offset
|
||||
, struct tm *(*method)(const time_t *,struct tm *)
|
||||
)
|
||||
{
|
||||
int success = 1;
|
||||
|
||||
char temp[100];
|
||||
int index = 0;
|
||||
int length = 0; /* |<-------->| */
|
||||
struct timeval tv;
|
||||
struct tm tm;
|
||||
int i = 0;
|
||||
int n_acc = 0;
|
||||
const uint16_t *accept = NULL;
|
||||
uint16_t a_ctype = REST.type.APPLICATION_JSON; /* for now json is default */
|
||||
|
||||
/* Looks like accepted content-type isn't currently supported */
|
||||
n_acc = REST.get_header_accept (request, &accept);
|
||||
for (i=0; i<n_acc; i++) {
|
||||
if ( accept [i] == REST.type.TEXT_PLAIN
|
||||
|| accept [i] == REST.type.APPLICATION_JSON
|
||||
)
|
||||
{
|
||||
a_ctype = accept [i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch(REST.get_method_type(request)){
|
||||
case METHOD_GET:
|
||||
gettimeofday (&tv, NULL);
|
||||
method (&tv.tv_sec, &tm);
|
||||
if (a_ctype == REST.type.TEXT_PLAIN) {
|
||||
index += sprintf
|
||||
( temp + index
|
||||
, "%lu-%02u-%02u %02u:%02u:%02u\n"
|
||||
, 1900 + tm.tm_year
|
||||
, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec
|
||||
);
|
||||
} else { // jSON Format
|
||||
index += sprintf
|
||||
( temp + index
|
||||
,"{\n \"localtime\" : \"%lu-%02u-%02u %02u:%02u:%02u\"\n"
|
||||
, 1900 + tm.tm_year
|
||||
, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec
|
||||
);
|
||||
index += sprintf(temp + index,"}\n");
|
||||
}
|
||||
length = strlen(temp);
|
||||
memcpy (buffer, temp, length);
|
||||
REST.set_header_content_type (response, REST.type.APPLICATION_JSON);
|
||||
REST.set_response_payload (response, buffer, length);
|
||||
|
||||
break;
|
||||
default:
|
||||
success = 0;
|
||||
}
|
||||
if (!success) {
|
||||
REST.set_response_status(response, REST.status.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
localtime_handler
|
||||
( void* request
|
||||
, void* response
|
||||
, uint8_t *buffer
|
||||
, uint16_t preferred_size
|
||||
, int32_t *offset
|
||||
)
|
||||
{
|
||||
return time_handler
|
||||
(request, response, buffer, preferred_size, offset, &localtime_r);
|
||||
}
|
||||
|
||||
void
|
||||
gmtime_handler
|
||||
( void* request
|
||||
, void* response
|
||||
, uint8_t *buffer
|
||||
, uint16_t preferred_size
|
||||
, int32_t *offset
|
||||
)
|
||||
{
|
||||
return time_handler
|
||||
(request, response, buffer, preferred_size, offset, &gmtime_r);
|
||||
}
|
158
apps/time/resource_timestamp.c
Normal file
158
apps/time/resource_timestamp.c
Normal file
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* \file
|
||||
* Resource for timestamp handling
|
||||
* \author
|
||||
* Ralf Schlatterbeck <rsc@runtux.com>
|
||||
*
|
||||
* \brief get/put time in seconds since 1970 (UNIX time)
|
||||
* Note: the internal format of the time in seconds is a 64bit number
|
||||
* unfortunately javascript (json) will only support double for which
|
||||
* the mantissa isn't long enough for representing that number. So we're
|
||||
* back to 32 bit and have a year 2038 problem.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "contiki.h"
|
||||
#include "time.h"
|
||||
#include "time_resource.h"
|
||||
#include "jsonparse.h"
|
||||
/* Only coap 13 for now */
|
||||
#include "er-coap-13.h"
|
||||
|
||||
/* Error-handling macro */
|
||||
# define BYE(_exp, _tag) \
|
||||
do { \
|
||||
PRINTF("Expect "_exp": %d\n",_tag); \
|
||||
success=0; \
|
||||
goto bye; \
|
||||
} while(0)
|
||||
|
||||
#define DEBUG 1
|
||||
#if DEBUG
|
||||
#define PRINTF(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define PRINTF(...)
|
||||
#endif
|
||||
|
||||
RESOURCE \
|
||||
( timestamp, METHOD_GET | METHOD_PUT
|
||||
, "clock/timestamp"
|
||||
, "title=\"Time\";rt=\"timestamp\""
|
||||
);
|
||||
|
||||
void
|
||||
timestamp_handler
|
||||
( void* request
|
||||
, void* response
|
||||
, uint8_t *buffer
|
||||
, uint16_t preferred_size
|
||||
, int32_t *offset
|
||||
)
|
||||
{
|
||||
int success = 1;
|
||||
|
||||
int i;
|
||||
char temp[100];
|
||||
int index = 0;
|
||||
int length = 0;
|
||||
int tag = 0;
|
||||
const uint8_t *bytes = NULL;
|
||||
size_t len = 0;
|
||||
struct timeval tv;
|
||||
int n_acc = 0;
|
||||
const uint16_t *accept = NULL;
|
||||
uint16_t a_ctype = REST.type.APPLICATION_JSON;
|
||||
uint16_t c_ctype = REST.get_header_content_type (request);
|
||||
|
||||
/* Seems like accepted type is currently unsupported? */
|
||||
n_acc = REST.get_header_accept (request, &accept);
|
||||
for (i=0; i<n_acc; i++) {
|
||||
if ( accept [i] == REST.type.TEXT_PLAIN
|
||||
|| accept [i] == REST.type.APPLICATION_JSON
|
||||
)
|
||||
{
|
||||
a_ctype = accept [i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch(REST.get_method_type(request)) {
|
||||
case METHOD_GET:
|
||||
gettimeofday (&tv, NULL);
|
||||
// TEXT format
|
||||
if (a_ctype == REST.type.TEXT_PLAIN) {
|
||||
index += sprintf (temp + index, "%ld\n", (long)tv.tv_sec);
|
||||
} else { // jSON Format
|
||||
// FIXME: Platform doesn't seem to support long long printing
|
||||
// We get empty string
|
||||
index += sprintf
|
||||
( temp + index
|
||||
,"{\n \"timestamp\" : \"%ld\"\n}\n"
|
||||
, (long)tv.tv_sec
|
||||
);
|
||||
}
|
||||
length = strlen(temp);
|
||||
memcpy (buffer, temp, length);
|
||||
REST.set_header_content_type (response, a_ctype);
|
||||
REST.set_response_payload (response, buffer, length);
|
||||
|
||||
break;
|
||||
case METHOD_PUT:
|
||||
if ((len = coap_get_payload(request, &bytes))) {
|
||||
if (c_ctype == REST.type.TEXT_PLAIN) {
|
||||
temp [sizeof (temp) - 1] = 0;
|
||||
strncpy (temp, (char *)bytes, MIN (len, sizeof (temp) - 1));
|
||||
} else { // jSON Format
|
||||
struct jsonparse_state state;
|
||||
struct jsonparse_state *parser = &state;
|
||||
PRINTF ("PUT: len: %d, %s\n", len, (char *)bytes);
|
||||
jsonparse_setup (parser, (char *)bytes, len);
|
||||
if ((tag = jsonparse_next (parser)) != JSON_TYPE_OBJECT) {
|
||||
BYE ("OBJECT", tag);
|
||||
}
|
||||
if ((tag = jsonparse_next (parser)) != JSON_TYPE_PAIR_NAME) {
|
||||
BYE ("PAIR_NAME", tag);
|
||||
}
|
||||
while (jsonparse_strcmp_value (parser, "timestamp") != 0) {
|
||||
tag = jsonparse_next (parser);
|
||||
if (tag != JSON_TYPE_PAIR) {
|
||||
BYE ("PAIR", tag);
|
||||
}
|
||||
tag = jsonparse_next (parser);
|
||||
tag = jsonparse_next (parser);
|
||||
if (tag != ',') {
|
||||
BYE (",", tag);
|
||||
}
|
||||
tag = jsonparse_next (parser);
|
||||
if (tag != JSON_TYPE_PAIR_NAME) {
|
||||
BYE ("PAIR_NAME", tag);
|
||||
}
|
||||
}
|
||||
tag = jsonparse_next (parser);
|
||||
if (tag != JSON_TYPE_PAIR) {
|
||||
BYE ("PAIR", tag);
|
||||
}
|
||||
tag = jsonparse_next (parser);
|
||||
if (tag != JSON_TYPE_STRING) {
|
||||
BYE ("STRING", tag);
|
||||
}
|
||||
jsonparse_copy_value (parser, temp, sizeof (temp));
|
||||
}
|
||||
// FIXME: Platform has no strtoll (long long)?
|
||||
tv.tv_sec = strtol (temp, NULL, 10);
|
||||
settimeofday (&tv, NULL);
|
||||
REST.set_response_status(response, REST.status.CHANGED);
|
||||
} else {
|
||||
success = 0;
|
||||
}
|
||||
bye :
|
||||
break;
|
||||
default:
|
||||
success = 0;
|
||||
}
|
||||
if (!success) {
|
||||
REST.set_response_status(response, REST.status.BAD_REQUEST);
|
||||
}
|
||||
}
|
165
apps/time/time.c
Normal file
165
apps/time/time.c
Normal file
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* \addgroup Time related functions
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include "contiki.h"
|
||||
#include "contiki-lib.h"
|
||||
#include "time.h"
|
||||
|
||||
/* Used for gmtime and localtime, according to manpage on linux the
|
||||
* internal value may be overwritten "by subsequent calls to any of the
|
||||
* date and time functions".
|
||||
*/
|
||||
static struct tm tm;
|
||||
|
||||
/*
|
||||
* Internal variables to manage offset of utc from the contiki clock
|
||||
* and timezone offset from utc in minutes.
|
||||
* For now we don't manage the sub-second offset -- time of setting the
|
||||
* clock and the precisiont of the clock in use don't warrant this
|
||||
* effort.
|
||||
* The last_seconds is used to check if we had a seconds overflow,
|
||||
* although this happens only every 136 years :-)
|
||||
*/
|
||||
time_t clock_offset;
|
||||
uint32_t last_seconds;
|
||||
int16_t minuteswest;
|
||||
|
||||
# define LEAP_YEAR(_year) \
|
||||
((_year % 4) == 0 && (_year % 100 != 0 || _year % 400 == 0))
|
||||
# define YDAYS(_year) (LEAP_YEAR(year) ? 366 : 365)
|
||||
|
||||
struct tm *
|
||||
gmtime_r (const time_t *timep, struct tm *ptm)
|
||||
{
|
||||
unsigned int year;
|
||||
int days, month, month_len;
|
||||
time_t t = *timep;
|
||||
ptm->tm_sec = t % 60;
|
||||
t /= 60;
|
||||
ptm->tm_min = t % 60;
|
||||
t /= 60;
|
||||
ptm->tm_hour = t % 24;
|
||||
t /= 24;
|
||||
ptm->tm_wday = (t+4) % 7;
|
||||
year = 70;
|
||||
days = 0;
|
||||
while ((days += YDAYS (year)) <= t)
|
||||
{
|
||||
year++;
|
||||
}
|
||||
ptm->tm_year = year;
|
||||
days -= YDAYS(year);
|
||||
t -= days;
|
||||
ptm->tm_yday = t;
|
||||
for (month=0; month<12; month++)
|
||||
{
|
||||
if (month == 1)
|
||||
{
|
||||
month_len = LEAP_YEAR(year) ? 29 : 28;
|
||||
}
|
||||
else
|
||||
{
|
||||
int m = month;
|
||||
if (m >= 7)
|
||||
{
|
||||
m -= 1;
|
||||
}
|
||||
m &= 1;
|
||||
month_len = m ? 30 : 31;
|
||||
}
|
||||
if (t >= month_len)
|
||||
{
|
||||
t -= month_len;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
ptm->tm_mon = month;
|
||||
ptm->tm_mday = t + 1;
|
||||
ptm->tm_isdst = 0;
|
||||
return ptm;
|
||||
}
|
||||
|
||||
struct tm *
|
||||
gmtime (const time_t *timep)
|
||||
{
|
||||
return gmtime_r (timep, &tm);
|
||||
}
|
||||
|
||||
struct tm *
|
||||
localtime_r (const time_t *timep, struct tm *ptm)
|
||||
{
|
||||
time_t t = *timep;
|
||||
t += minuteswest * 60;
|
||||
return gmtime_r (&t, ptm);
|
||||
}
|
||||
|
||||
struct tm *
|
||||
localtime (const time_t *timep)
|
||||
{
|
||||
return localtime_r (timep, &tm);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get time in seconds and microseconds
|
||||
* gettimeofday will return the clock time as the microseconds part
|
||||
* while settimeofday will *ignore* the microseconds part (for now).
|
||||
* Note that the contiki clock interface is broken anyway, we can't read
|
||||
* seconds and sub-seconds atomically. We try to work around this by
|
||||
* repeatedly reading seconds, sub-seconds, seconds until first and
|
||||
* second read of seconds match.
|
||||
*/
|
||||
int
|
||||
gettimeofday (struct timeval *tv, struct timezone *tz)
|
||||
{
|
||||
uint32_t cs;
|
||||
if (tz) {
|
||||
tz->tz_minuteswest = minuteswest;
|
||||
tz->tz_dsttime = 0;
|
||||
}
|
||||
if (tv) {
|
||||
int i;
|
||||
/* Limit tries to get the same second twice to two */
|
||||
for (i=0; i<2; i++) {
|
||||
cs = clock_seconds ();
|
||||
if (cs < last_seconds) {
|
||||
clock_offset += 0xFFFFFFFFL;
|
||||
clock_offset ++;
|
||||
}
|
||||
last_seconds = cs;
|
||||
tv->tv_sec = cs + clock_offset;
|
||||
tv->tv_usec = ((time_t)(clock_time () % CLOCK_SECOND))
|
||||
* 1000000L / CLOCK_SECOND;
|
||||
if (cs == clock_seconds ()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set time in seconds, microseconds ignored for now
|
||||
*/
|
||||
int
|
||||
settimeofday (const struct timeval *tv, const struct timezone *tz)
|
||||
{
|
||||
/* Don't allow setting timezone */
|
||||
if (tz) {
|
||||
errno = ERANGE;
|
||||
return -1;
|
||||
}
|
||||
if (tv) {
|
||||
uint32_t cs;
|
||||
cs = clock_seconds ();
|
||||
clock_offset = tv->tv_sec - cs;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @} */
|
65
apps/time/time.h
Normal file
65
apps/time/time.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* \defgroup Time related functions
|
||||
*
|
||||
* This rolls the necessary definition for getting/setting time and
|
||||
* managing local time into one include file, on posix systems this
|
||||
* lives in at least two include files, time.h and sys/time.h
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Definitions for the time module
|
||||
*
|
||||
* \author
|
||||
* Ralf Schlatterbeck <rsc@tux.runtux.com>
|
||||
*/
|
||||
|
||||
#ifndef time_h
|
||||
#define time_h
|
||||
|
||||
|
||||
typedef signed long long time_t;
|
||||
typedef signed long suseconds_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct tm {
|
||||
uint32_t tm_year; /* year */
|
||||
uint16_t tm_yday; /* day in the year */
|
||||
uint8_t tm_sec; /* seconds */
|
||||
uint8_t tm_min; /* minutes */
|
||||
uint8_t tm_hour; /* hours */
|
||||
uint8_t tm_mday; /* day of the month */
|
||||
uint8_t tm_mon; /* month */
|
||||
uint8_t tm_wday; /* day of the week */
|
||||
uint8_t tm_isdst; /* daylight saving time */
|
||||
};
|
||||
|
||||
struct timeval {
|
||||
time_t tv_sec; /* seconds */
|
||||
suseconds_t tv_usec; /* microseconds */
|
||||
};
|
||||
|
||||
struct timezone {
|
||||
int16_t tz_minuteswest; /* minutes west of Greenwich */
|
||||
int tz_dsttime; /* type of DST correction, unused */
|
||||
};
|
||||
|
||||
struct tm *gmtime (const time_t *timep);
|
||||
struct tm *gmtime_r (const time_t *timep, struct tm *result);
|
||||
struct tm *localtime (const time_t *timep);
|
||||
struct tm *localtime_r (const time_t *timep, struct tm *result);
|
||||
|
||||
int gettimeofday (struct timeval *tv, struct timezone *tz);
|
||||
int settimeofday (const struct timeval *tv, const struct timezone *tz);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // time_h
|
||||
/** @} */
|
27
apps/time/time_resource.h
Normal file
27
apps/time/time_resource.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* \addgroup Time related functions
|
||||
*
|
||||
* Resource definitions for time module
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Resource definitions for the time module
|
||||
*
|
||||
* \author
|
||||
* Ralf Schlatterbeck <rsc@tux.runtux.com>
|
||||
*/
|
||||
|
||||
#ifndef time_resource_h
|
||||
#define time_resource_h
|
||||
#include "contiki.h"
|
||||
#include "erbium.h"
|
||||
|
||||
extern resource_t resource_timestamp;
|
||||
extern resource_t resource_localtime;
|
||||
extern resource_t resource_gmtime;
|
||||
|
||||
#endif // time_resource_h
|
||||
/** @} */
|
Loading…
Add table
Add a link
Reference in a new issue