diff --git a/bin/logger/Makefile.am b/bin/logger/Makefile.am index 876883c..ade62ea 100644 --- a/bin/logger/Makefile.am +++ b/bin/logger/Makefile.am @@ -2,8 +2,8 @@ AM_CFLAGS = -Wall -D_REENTRANT -std=gnu99 $(DEPS_VZ_CFLAGS) AM_CPPFLAGS = -I$(top_srcdir)/include -Iinclude bin_PROGRAMS = vzlogger -vzlogger_SOURCES = src/vzlogger.c src/channel.c src/api.c src/configuration.c src/threads.c src/buffer.c -vzlogger_LDADD = $(top_srcdir)/src/libmeter.a +vzlogger_SOURCES = src/vzlogger.c src/channel.c src/api.c src/config.c src/threads.c src/buffer.c +vzlogger_LDADD = $(top_srcdir)/src/libmtr.a vzlogger_LDFLAGS = -lpthread -lm $(DEPS_VZ_LIBS) # local interface support diff --git a/bin/logger/Makefile.in b/bin/logger/Makefile.in index 707c3b8..6ca136f 100644 --- a/bin/logger/Makefile.in +++ b/bin/logger/Makefile.in @@ -57,16 +57,16 @@ CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(bindir)" PROGRAMS = $(bin_PROGRAMS) am__vzlogger_SOURCES_DIST = src/vzlogger.c src/channel.c src/api.c \ - src/configuration.c src/threads.c src/buffer.c src/local.c + src/config.c src/threads.c src/buffer.c src/local.c @LOCAL_SUPPORT_TRUE@am__objects_1 = local.$(OBJEXT) am_vzlogger_OBJECTS = vzlogger.$(OBJEXT) channel.$(OBJEXT) \ - api.$(OBJEXT) configuration.$(OBJEXT) threads.$(OBJEXT) \ + api.$(OBJEXT) config.$(OBJEXT) threads.$(OBJEXT) \ buffer.$(OBJEXT) $(am__objects_1) vzlogger_OBJECTS = $(am_vzlogger_OBJECTS) am__DEPENDENCIES_1 = @LOCAL_SUPPORT_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) @SML_SUPPORT_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1) -vzlogger_DEPENDENCIES = $(top_srcdir)/src/libmeter.a \ +vzlogger_DEPENDENCIES = $(top_srcdir)/src/libmtr.a \ $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3) vzlogger_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(vzlogger_LDFLAGS) \ $(LDFLAGS) -o $@ @@ -182,9 +182,9 @@ top_srcdir = @top_srcdir@ AM_CFLAGS = -Wall -D_REENTRANT -std=gnu99 $(DEPS_VZ_CFLAGS) \ $(am__append_3) $(am__append_5) AM_CPPFLAGS = -I$(top_srcdir)/include -Iinclude -vzlogger_SOURCES = src/vzlogger.c src/channel.c src/api.c \ - src/configuration.c src/threads.c src/buffer.c $(am__append_1) -vzlogger_LDADD = $(top_srcdir)/src/libmeter.a $(am__append_2) \ +vzlogger_SOURCES = src/vzlogger.c src/channel.c src/api.c src/config.c \ + src/threads.c src/buffer.c $(am__append_1) +vzlogger_LDADD = $(top_srcdir)/src/libmtr.a $(am__append_2) \ $(am__append_4) vzlogger_LDFLAGS = -lpthread -lm $(DEPS_VZ_LIBS) all: all-am @@ -271,7 +271,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/api.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buffer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/channel.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/configuration.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/local.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/threads.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vzlogger.Po@am__quote@ @@ -332,19 +332,19 @@ api.obj: src/api.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o api.obj `if test -f 'src/api.c'; then $(CYGPATH_W) 'src/api.c'; else $(CYGPATH_W) '$(srcdir)/src/api.c'; fi` -configuration.o: src/configuration.c -@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT configuration.o -MD -MP -MF $(DEPDIR)/configuration.Tpo -c -o configuration.o `test -f 'src/configuration.c' || echo '$(srcdir)/'`src/configuration.c -@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/configuration.Tpo $(DEPDIR)/configuration.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='src/configuration.c' object='configuration.o' libtool=no @AMDEPBACKSLASH@ +config.o: src/config.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT config.o -MD -MP -MF $(DEPDIR)/config.Tpo -c -o config.o `test -f 'src/config.c' || echo '$(srcdir)/'`src/config.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/config.Tpo $(DEPDIR)/config.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='src/config.c' object='config.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o configuration.o `test -f 'src/configuration.c' || echo '$(srcdir)/'`src/configuration.c +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o config.o `test -f 'src/config.c' || echo '$(srcdir)/'`src/config.c -configuration.obj: src/configuration.c -@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT configuration.obj -MD -MP -MF $(DEPDIR)/configuration.Tpo -c -o configuration.obj `if test -f 'src/configuration.c'; then $(CYGPATH_W) 'src/configuration.c'; else $(CYGPATH_W) '$(srcdir)/src/configuration.c'; fi` -@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/configuration.Tpo $(DEPDIR)/configuration.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='src/configuration.c' object='configuration.obj' libtool=no @AMDEPBACKSLASH@ +config.obj: src/config.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT config.obj -MD -MP -MF $(DEPDIR)/config.Tpo -c -o config.obj `if test -f 'src/config.c'; then $(CYGPATH_W) 'src/config.c'; else $(CYGPATH_W) '$(srcdir)/src/config.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/config.Tpo $(DEPDIR)/config.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='src/config.c' object='config.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o configuration.obj `if test -f 'src/configuration.c'; then $(CYGPATH_W) 'src/configuration.c'; else $(CYGPATH_W) '$(srcdir)/src/configuration.c'; fi` +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o config.obj `if test -f 'src/config.c'; then $(CYGPATH_W) 'src/config.c'; else $(CYGPATH_W) '$(srcdir)/src/config.c'; fi` threads.o: src/threads.c @am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT threads.o -MD -MP -MF $(DEPDIR)/threads.Tpo -c -o threads.o `test -f 'src/threads.c' || echo '$(srcdir)/'`src/threads.c diff --git a/bin/logger/include/buffer.h b/bin/logger/include/buffer.h index 4950286..c233b7e 100644 --- a/bin/logger/include/buffer.h +++ b/bin/logger/include/buffer.h @@ -44,7 +44,7 @@ typedef struct { pthread_mutex_t mutex; } buffer_t; -/* Prototypes */ +/* prototypes */ void buffer_init(buffer_t *buf); reading_t * buffer_push(buffer_t *buf, reading_t *rd); void buffer_free(buffer_t *buf); diff --git a/bin/logger/include/channel.h b/bin/logger/include/channel.h index f1a1675..ce2b9b4 100644 --- a/bin/logger/include/channel.h +++ b/bin/logger/include/channel.h @@ -32,21 +32,27 @@ #include "vzlogger.h" #include "buffer.h" -typedef struct { +typedef struct channel { char id[5]; /* only for internal usage & debugging */ - char *middleware; /* url to middleware */ - char *uuid; /* unique identifier for middleware */ - + reading_id_t identifier; /* channel identifier (OBIS, string) */ buffer_t buffer; /* circular queue to buffer readings */ pthread_cond_t condition; /* pthread syncronization to notify logging thread and local webserver */ pthread_t thread; /* pthread for asynchronus logging */ - pthread_status_t status; + pthread_status_t status; /* status of thread */ + + char *middleware; /* url to middleware */ + char *uuid; /* unique identifier for middleware */ + + double last; /* last counter value */ + int counter:1; /* TRUE if we want to send the diffrence between to values */ } channel_t; -/* Prototypes */ -void channel_init(channel_t *ch, const char *uuid, const char *middleware, reading_id_t identifier); +/* prototypes */ +void channel_init(channel_t *ch, const char *uuid, const char *middleware, reading_id_t identifier, int counter); void channel_free(channel_t *ch); +reading_t * channel_add_readings(channel_t *ch, meter_protocol_t protocol, reading_t *rds, size_t n); + #endif /* _CHANNEL_H_ */ diff --git a/bin/logger/include/config.h b/bin/logger/include/config.h new file mode 100644 index 0000000..c168e84 --- /dev/null +++ b/bin/logger/include/config.h @@ -0,0 +1,94 @@ +/** + * Parsing commandline options and channel list + * + * @author Steffen Vogel + * @copyright Copyright (c) 2011, The volkszaehler.org project + * @package vzlogger + * @license http://opensource.org/licenses/gpl-license.php GNU Public License + */ +/* + * This file is part of volkzaehler.org + * + * volkzaehler.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * volkzaehler.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with volkszaehler.org. If not, see . + */ + +#ifndef _CONFIGURATION_H_ +#define _CONFIGURATION_H_ + +#include + +#include +#include + +/** + * General options from CLI + */ +typedef struct { + char *config; /* filename of configuration */ + char *log; /* filename for logging */ + FILE *logfd; + + int port; /* TCP port for local interface */ + int verbosity; /* verbosity level */ + int comet_timeout; /* in seconds; */ + int buffer_length; /* in seconds; how long to buffer readings for local interfalce */ + int retry_pause; /* in seconds; how long to pause after an unsuccessful HTTP request */ + + /* boolean bitfields, padding at the end of struct */ + int channel_index:1; /* give a index of all available channels via local interface */ + int daemon:1; /* run in background */ + int foreground:1; /* dont fork in background */ + int local:1; /* enable local interface */ + int logging:1; /* start logging threads, depends on local & daemon */ +} config_options_t; + +/* forward declarartions */ +struct map; +struct channel; + +/** + * Reads key, value and type from JSON object + * + * @param jso the JSON object + * @param key the key of the option + * @param option a pointer to a uninitialized option_t structure + * @return 0 on succes, <0 on error + */ +int option_init(struct json_object *jso, char *key, option_t *option); + +/** + * Validate UUID + * Match against something like: '550e8400-e29b-11d4-a716-446655440000' + * + * @param const char *uuid a string containing to uuid + * @return int non-zero on success + */ +int config_validate_uuid(const char *uuid); + +/** + * Parse JSON formatted configuration file + * + * @param const char *filename the path of the configuration file + * @param list_t *mappings a pointer to a list, where new channel<->meter mappings should be stored + * @param config_options_t *options a pointer to a structure of global configuration options + * @return int non-zero on success + */ +int config_parse(const char *filename, list_t *mappings, config_options_t *options); + +struct channel * config_parse_channel(struct json_object *jso); +struct map * config_parse_meter(struct json_object *jso); + + + +#endif /* _CONFIGURATION_H_ */ diff --git a/bin/logger/include/local.h b/bin/logger/include/local.h index 51ec990..eedd6e7 100644 --- a/bin/logger/include/local.h +++ b/bin/logger/include/local.h @@ -26,7 +26,9 @@ #ifndef _LOCAL_H_ #define _LOCAL_H_ -#include /* required for MHD */ +#include /* required for libMHD */ +#include /* required for libMHD */ + #include int handle_request( diff --git a/bin/logger/include/vzlogger.h b/bin/logger/include/vzlogger.h index 38e974d..01039d3 100644 --- a/bin/logger/include/vzlogger.h +++ b/bin/logger/include/vzlogger.h @@ -27,64 +27,40 @@ #define _VZLOGGER_H_ #include + #include +#include +#include #include "config.h" #include "list.h" -enum { - LOG_ERROR = -1, - LOG_STD = 0, - LOG_INFO = 5, - LOG_DEBUG = 10, - LOG_FINEST = 15 -}; - +/* enumerations */ typedef enum { - UNKNOWN, - RUNNING, - TERMINATED, - CANCELED + status_unknown, + status_running, + status_terminated, + status__cancelled } pthread_status_t; /** - * Type for associating channels to meters + * Type for mapping channels to meters */ -typedef struct { +typedef struct map { meter_t meter; list_t channels; - int interval; pthread_t thread; pthread_status_t status; -} assoc_t; +} map_t; -/** - * Options from command line - */ -typedef struct { - char *config; /* filename of configuration */ - char *log; /* filename for logging */ - FILE *logfd; - - int port; /* TCP port for local interface */ - int verbosity; /* verbosity level */ - int comet_timeout; /* in seconds; */ - int buffer_length; /* in seconds; how long to buffer readings for local interfalce */ - int retry_pause; /* in seconds; how long to pause after an unsuccessful HTTP request */ - - /* boolean bitfields, padding at the end of struct */ - int channel_index:1; /* give a index of all available channels via local interface */ - int daemon:1; /* run in background */ - int foreground:1; /* dont fork in background */ - int local:1; /* enable local interface */ - int logging:1; /* start logging threads, depends on local & daemon */ -} options_t; - -/* Prototypes */ -void print(int level, const char *format, void *id, ... ); -void usage(char ** argv); +/* prototypes */ void quit(int sig); -void parse_options(int argc, char *argv[], options_t *options); +void daemonize(); + +void show_usage(char ** argv); +void show_aliases(); + +int options_parse(int argc, char *argv[], config_options_t *options); #endif /* _VZLOGGER_H_ */ diff --git a/bin/logger/src/api.c b/bin/logger/src/api.c index 032ab62..a5c11e1 100644 --- a/bin/logger/src/api.c +++ b/bin/logger/src/api.c @@ -33,7 +33,7 @@ #include "api.h" #include "vzlogger.h" -extern options_t options; +extern config_options_t options; int curl_custom_debug_callback(CURL *curl, curl_infotype type, char *data, size_t size, void *arg) { channel_t *ch = (channel_t *) arg; @@ -45,17 +45,17 @@ int curl_custom_debug_callback(CURL *curl, curl_infotype type, char *data, size_ case CURLINFO_TEXT: case CURLINFO_END: if (end) *end = '\0'; /* terminate without \n */ - print(11, "CURL: %.*s", ch, (int) size, data); + print(log_debug+5, "CURL: %.*s", ch, (int) size, data); break; case CURLINFO_SSL_DATA_IN: case CURLINFO_DATA_IN: - print(14, "CURL: Received %lu bytes", ch, (unsigned long) size); + print(log_debug+5, "CURL: Received %lu bytes", ch, (unsigned long) size); break; case CURLINFO_SSL_DATA_OUT: case CURLINFO_DATA_OUT: - print(14, "CURL: Sent %lu bytes.. ", ch, (unsigned long) size); + print(log_debug+5, "CURL: Sent %lu bytes.. ", ch, (unsigned long) size); break; case CURLINFO_HEADER_IN: @@ -72,7 +72,7 @@ size_t curl_custom_write_callback(void *ptr, size_t size, size_t nmemb, void *da response->data = realloc(response->data, response->size + realsize + 1); if (response->data == NULL) { /* out of memory! */ - print(-1, "Not enough memory", NULL); + print(log_error, "Cannot allocate memory", NULL); exit(EXIT_FAILURE); } @@ -122,7 +122,7 @@ CURL * api_curl_init(channel_t *ch) { curl = curl_easy_init(); if (!curl) { - print(-1, "CURL: cannot create handle", ch); + print(log_error, "CURL: cannot create handle", ch); exit(EXIT_FAILURE); } diff --git a/bin/logger/src/buffer.c b/bin/logger/src/buffer.c index 149dcfb..be91cb1 100644 --- a/bin/logger/src/buffer.c +++ b/bin/logger/src/buffer.c @@ -76,7 +76,7 @@ reading_t * buffer_push(buffer_t *buf, reading_t *rd) { } new->next = NULL; - + buf->tail = new; buf->size++; pthread_mutex_unlock(&buf->mutex); diff --git a/bin/logger/src/channel.c b/bin/logger/src/channel.c index c12acc5..e5692c4 100644 --- a/bin/logger/src/channel.c +++ b/bin/logger/src/channel.c @@ -30,12 +30,13 @@ #include "channel.h" -void channel_init(channel_t *ch, const char *uuid, const char *middleware, reading_id_t identifier) { +void channel_init(channel_t *ch, const char *uuid, const char *middleware, reading_id_t identifier, int counter) { static int instances; /* static to generate channel ids */ snprintf(ch->id, 5, "ch%i", instances++); ch->identifier = identifier; - ch->status = UNKNOWN; + ch->status = status_unknown; + ch->counter = counter; ch->uuid = strdup(uuid); ch->middleware = strdup(middleware); @@ -50,7 +51,47 @@ void channel_init(channel_t *ch, const char *uuid, const char *middleware, readi void channel_free(channel_t *ch) { buffer_free(&ch->buffer); pthread_cond_destroy(&ch->condition); - + free(ch->uuid); free(ch->middleware); } + +reading_t * channel_add_readings(channel_t *ch, meter_protocol_t protocol, reading_t *rds, size_t n) { + reading_t *first = NULL; /* first unsent reading which has been added */ + + double last; + double value; + + for (int i = 0; i < n; i++) { + int add = FALSE; + + if (protocol == meter_protocol_d0 || protocol == meter_protocol_sml) { + if (obis_compare(rds[i].identifier.obis, ch->identifier.obis) == 0) { + add = TRUE; + } + } + else { /* no channel identifier, adding all readings to buffer */ + add = TRUE; + } + + if (add) { + value = rds[i].value; + + print(log_info, "Adding reading to queue (value=%.2f delta=%.2f ts=%.3f)", ch, value, value - last, tvtod(rds[i].time)); + + if (ch->counter) { + rds[i].value -= last; + } + + reading_t *added = buffer_push(&ch->buffer, &rds[i]); /* remember last value to calculate relative consumption */ + + if (first == NULL) { + first = added; + } + } + } + + ch->last = last; + + return first; +} diff --git a/bin/logger/src/config.c b/bin/logger/src/config.c new file mode 100644 index 0000000..d7900d3 --- /dev/null +++ b/bin/logger/src/config.c @@ -0,0 +1,288 @@ +/** + * Parsing Apache HTTPd-like configuration + * + * @author Steffen Vogel + * @copyright Copyright (c) 2011, The volkszaehler.org project + * @package vzlogger + * @license http://opensource.org/licenses/gpl-license.php GNU Public License + */ +/* + * This file is part of volkzaehler.org + * + * volkzaehler.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * volkzaehler.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with volkszaehler.org. If not, see . + */ + +#include +#include +#include + +#include "config.h" +#include "channel.h" + +static const char *option_type_str[] = { "null", "boolean", "double", "int", "object", "array", "string" }; + +int config_parse(const char *filename, list_t *mappings, config_options_t *options) { + struct json_object *json_cfg = NULL; + struct json_tokener *json_tok = json_tokener_new(); + + char buf[JSON_FILE_BUF_SIZE]; + int line = 0; + + /* open configuration file */ + FILE *file = fopen(filename, "r"); + if (file == NULL) { + print(log_error, "Cannot open configfile %s: %s", NULL, filename, strerror(errno)); /* why didn't the file open? */ + exit(EXIT_FAILURE); + } + else { + print(log_info, "Start parsing configuration from %s", NULL, filename); + } + + /* parse JSON */ + while(fgets(buf, JSON_FILE_BUF_SIZE, file)) { + line++; + + json_cfg = json_tokener_parse_ex(json_tok, buf, strlen(buf)); + + if (json_tok->err > 1) { + print(log_error, "Error in %s:%d %s at offset %d", NULL, filename, line, json_tokener_errors[json_tok->err], json_tok->char_offset); + exit(EXIT_FAILURE); + } + } + + /* householding */ + fclose(file); + json_tokener_free(json_tok); + + /* parse options */ + json_object_object_foreach(json_cfg, key, value) { + enum json_type type = json_object_get_type(value); + + if (strcmp(key, "daemon") == 0 && type == json_type_boolean) { + options->daemon = json_object_get_boolean(value); + } + else if (strcmp(key, "foreground") == 0 && type == json_type_boolean) { + options->foreground = json_object_get_boolean(value); + } + else if (strcmp(key, "log") == 0 && type == json_type_string) { + options->log = strdup(json_object_get_string(value)); + } + else if (strcmp(key, "retry") == 0 && type == json_type_int) { + options->retry_pause = json_object_get_int(value); + } + else if (strcmp(key, "verbosity") == 0 && type == json_type_int) { + options->verbosity = json_object_get_int(value); + } + else if (strcmp(key, "local") == 0) { + json_object_object_foreach(value, key, local_value) { + enum json_type local_type = json_object_get_type(local_value); + + if (strcmp(key, "enabled") == 0 && local_type == json_type_boolean) { + options->local = json_object_get_boolean(local_value); + } + else if (strcmp(key, "port") == 0 && local_type == json_type_int) { + options->port = json_object_get_int(local_value); + } + else if (strcmp(key, "timeout") == 0 && local_type == json_type_int) { + options->comet_timeout = json_object_get_int(local_value); + } + else if (strcmp(key, "buffer") == 0 && local_type == json_type_int) { + options->buffer_length = json_object_get_int(local_value); + } + else if (strcmp(key, "index") == 0 && local_type == json_type_boolean) { + options->channel_index = json_object_get_boolean(local_value); + } + else { + print(log_error, "Ignoring invalid field or type (%s=%s (%s))", + NULL, key, json_object_get_string(local_value), option_type_str[local_type]); + } + } + } + else if ((strcmp(key, "sensors") == 0 || strcmp(key, "meters") == 0) && type == json_type_array) { + int len = json_object_array_length(value); + for (int i = 0; i < len; i++) { + map_t *mapping = config_parse_meter(json_object_array_get_idx(value, i)); + if (mapping == NULL) { + return ERR; + } + + list_push(mappings, mapping); + } + } + else { + print(log_error, "Ignoring invalid field or type (%s=%s (%s))", + NULL, key, json_object_get_string(value), option_type_str[type]); + } + } + + json_object_put(json_cfg); /* free allocated memory */ + + return SUCCESS; +} + +map_t * config_parse_meter(struct json_object *jso) { + list_t json_channels; + list_t options; + list_init(&json_channels); + list_init(&options); + + json_object_object_foreach(jso, key, value) { + enum json_type type = json_object_get_type(value); + + if (strcmp(key, "channels") == 0 && type == json_type_array) { + int len = json_object_array_length(value); + for (int i = 0; i < len; i++) { + list_push(&json_channels, json_object_array_get_idx(value, i)); + } + } + else if (strcmp(key, "channel") == 0 && type == json_type_object) { + list_push(&json_channels, value); + } + else { /* all other options will be passed to meter_init() */ + option_t *option = malloc(sizeof(option_t)); + + if (option_init(value, key, option) != SUCCESS) { + print(log_error, "Ignoring invalid type (%s=%s (%s))", + NULL, key, json_object_get_string(value), option_type_str[type]); + } + + list_push(&options, option); + } + } + + /* init meter */ + map_t *mapping = malloc(sizeof(map_t)); + + list_init(&mapping->channels); + if (meter_init(&mapping->meter, options) != SUCCESS) { + print(log_error, "Failed to initialize meter. Arborting.", mapping); + return NULL; + } + + print(log_info, "New meter initialized (protocol=%s)", mapping, meter_get_details(mapping->meter.protocol)->name); + + /* init channels */ + struct json_object *json_channel; + while ((json_channel = list_pop(&json_channels)) != NULL) { + channel_t *ch = config_parse_channel(json_channel); + if (ch) list_push(&mapping->channels, ch); + } + + /* householding */ + list_free(&options); + + return mapping; +} + +channel_t * config_parse_channel(struct json_object *jso) { + const char *uuid = NULL; + const char *middleware = NULL; + const char *identifier = NULL; + int counter = FALSE; + + json_object_object_foreach(jso, key, value) { + enum json_type type = json_object_get_type(value); + + if (strcmp(key, "uuid") == 0 && type == json_type_string) { + uuid = json_object_get_string(value); + } + else if (strcmp(key, "middleware") == 0 && type == json_type_string) { + middleware = json_object_get_string(value); + } + else if (strcmp(key, "identifier") == 0 && type == json_type_string) { + identifier = json_object_get_string(value); + } + else if (strcmp(key, "counter") == 0 && type == json_type_boolean) { + counter = json_object_get_boolean(value); + } + else { + print(log_error, "Ignoring invalid field or type (%s=%s (%s))", + NULL, key, json_object_get_string(value), option_type_str[type]); + } + } + + if (uuid == NULL) { + print(log_error, "Missing UUID", NULL); + exit(EXIT_FAILURE); + } + else if (!config_validate_uuid(uuid)) { + print(log_error, "Invalid UUID: %s", NULL, uuid); + exit(EXIT_FAILURE); + } + else if (middleware == NULL) { + print(log_error, "Missing middleware", NULL); + exit(EXIT_FAILURE); + } + + // TODO other identifiers are not supported at the moment + reading_id_t id; + + if (identifier) { + if (obis_parse(identifier, &id.obis) != SUCCESS) { + if (obis_lookup_alias(identifier, &id.obis) != SUCCESS) { + print(log_error, "Invalid id: %s", NULL, identifier); + return NULL; + } + } + } + else { + obis_init(&id.obis, NULL); + } + + char obis_str[OBIS_STR_LEN]; + obis_unparse(id.obis, obis_str, 6*3+5+1); + + channel_t *ch = malloc(sizeof(channel_t)); + channel_init(ch, uuid, middleware, id, counter); + print(log_info, "New channel initialized (uuid=...%s middleware=%s id=%s counter=%s)", ch, uuid+30, middleware, obis_str, (counter) ? "yes" : "no"); + + return ch; +} + +int config_validate_uuid(const char *uuid) { + for (const char *p = uuid; *p; p++) { + switch (p - uuid) { + case 8: + case 13: + case 18: + case 23: + if (*p != '-') return FALSE; + else break; + + default: + if (!isxdigit(*p)) return FALSE; + else break; + } + } + return TRUE; +} + +int option_init(struct json_object *jso, char *key, option_t *option) { + option_value_t val; + + switch (json_object_get_type(jso)) { + case json_type_string: val.string = json_object_get_string(jso); break; + case json_type_int: val.integer = json_object_get_int(jso); break; + case json_type_boolean: val.boolean = json_object_get_boolean(jso); break; + case json_type_double: val.floating = json_object_get_double(jso); break; + default: return ERR; + } + + option->key = key; + option->type = json_object_get_type(jso); + option->value = val; + + return SUCCESS; +} + diff --git a/bin/logger/src/local.c b/bin/logger/src/local.c index c6bf99a..df3c952 100644 --- a/bin/logger/src/local.c +++ b/bin/logger/src/local.c @@ -33,7 +33,7 @@ #include "local.h" #include "api.h" -extern options_t options; +extern config_options_t options; int handle_request(void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) { @@ -41,12 +41,12 @@ int handle_request(void *cls, struct MHD_Connection *connection, const char *url int status; int response_code = MHD_HTTP_NOT_FOUND; - list_t *assocs = (list_t *) cls; + list_t *mappings = (list_t *) cls; struct MHD_Response *response; const char *mode = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "mode"); - print(2, "Local request received: method=%s url=%s mode=%s", "http", method, url, mode); + print(log_info+1, "Local request received: method=%s url=%s mode=%s", "http", method, url, mode); if (strcmp(method, "GET") == 0) { struct timespec ts; @@ -67,17 +67,13 @@ int handle_request(void *cls, struct MHD_Connection *connection, const char *url else { json_exception = json_object_new_object(); - json_object_object_add(json_exception, "message", json_object_new_string("channel indexing is disabled")); + json_object_object_add(json_exception, "message", json_object_new_string("channel index is disabled")); json_object_object_add(json_exception, "code", json_object_new_int(0)); } } - foreach(*assocs, it) { - assoc_t *assoc = (assoc_t *) it->data; - - foreach(assoc->channels, it) { - channel_t *ch = (channel_t *) it->data; - + foreach(*mappings, mapping, map_t) { + foreach(mapping->channels, ch, channel_t) { if (strcmp(ch->uuid, uuid) == 0 || show_all) { response_code = MHD_HTTP_OK; @@ -97,8 +93,9 @@ int handle_request(void *cls, struct MHD_Connection *connection, const char *url json_object_object_add(json_ch, "uuid", json_object_new_string(ch->uuid)); json_object_object_add(json_ch, "middleware", json_object_new_string(ch->middleware)); - json_object_object_add(json_ch, "interval", json_object_new_int(assoc->interval)); - json_object_object_add(json_ch, "protocol", json_object_new_string(assoc->meter.type->name)); + json_object_object_add(json_ch, "last", json_object_new_double(ch->last)); + json_object_object_add(json_ch, "interval", json_object_new_int(mapping->meter.interval)); + json_object_object_add(json_ch, "protocol", json_object_new_string(meter_get_details(mapping->meter.protocol)->name)); struct json_object *json_tuples = api_json_tuples(&ch->buffer, ch->buffer.head, ch->buffer.tail); json_object_object_add(json_ch, "tuples", json_tuples); diff --git a/bin/logger/src/threads.c b/bin/logger/src/threads.c index 45d1fe9..aa841dd 100644 --- a/bin/logger/src/threads.c +++ b/bin/logger/src/threads.c @@ -30,58 +30,60 @@ #include "api.h" #include "vzlogger.h" -extern options_t options; +extern config_options_t options; void reading_thread_cleanup(void *rds) { free(rds); } void * reading_thread(void *arg) { - assoc_t *assoc = (assoc_t *) arg; - meter_t *mtr = &assoc->meter; - reading_t *rds = malloc(sizeof(reading_t) * mtr->type->max_readings); + reading_t *rds; + map_t *mapping; + meter_t *mtr; time_t last, delta; + const meter_details_t *details; size_t n = 0; + mapping = (map_t *) arg; + mtr = &mapping->meter; + details = meter_get_details(mtr->protocol); + + /* allocate memory for readings */ + size_t bytes = sizeof(reading_t) * details->max_readings; + rds = malloc(bytes); + memset(rds, 0, bytes); + pthread_cleanup_push(&reading_thread_cleanup, rds); do { /* start thread main loop */ /* fetch readings from meter and measure interval */ last = time(NULL); - n = meter_read(mtr, rds, mtr->type->max_readings); + n = meter_read(mtr, rds, details->max_readings); delta = time(NULL) - last; - /* update buffer length with current interval */ - if (!mtr->type->periodic && delta != assoc->interval) { - print(15, "Updating interval to %i", mtr, delta); - assoc->interval = delta; + /* dumping meter output */ + if (options.verbosity > log_debug) { + print(log_debug, "Got %i new readings from meter:", mtr, n); + char obis_str[OBIS_STR_LEN]; + for (int i = 0; i < n; i++) { + obis_unparse(rds[i].identifier.obis, obis_str, OBIS_STR_LEN); + print(log_debug, "Reading: id=%s value=%.2f ts=%.3f", mtr, obis_str, rds[i].value, tvtod(rds[i].time)); + } } - foreach(assoc->channels, it) { - channel_t *ch = (channel_t *) it->data; + /* update buffer length with current interval */ + if (!details->periodic && mtr->interval != delta) { + print(log_debug, "Updating interval to %i", mtr, delta); + mtr->interval = delta; + } + + foreach(mapping->channels, ch, channel_t) { buffer_t *buf = &ch->buffer; - reading_t *added = NULL; - - for (int i = 0; i < n; i++) { - switch (mtr->type->id) { - case SML: - case D0: - if (obis_compare(rds[i].identifier.obis, ch->identifier.obis) == 0) { - print(5, "New reading (value=%.2f ts=%f)", ch, ch->id, rds[i].value, tvtod(rds[i].time)); - added = buffer_push(buf, &rds[i]); - } - break; - - default: - /* no channel identifier, adding all readings to buffer */ - print(LOG_INFO, "New reading (value=%.2f ts=%f)", ch, ch->id, rds[i].value, tvtod(rds[i].time)); - added = buffer_push(buf, &rds[i]); - } - } + reading_t *added = channel_add_readings(ch, mtr->protocol, rds, n); /* update buffer length to interval */ - if (options.local && assoc->interval != 0) { - ch->buffer.keep = ceil(options.buffer_length / assoc->interval); + if (options.local) { + buf->keep = ceil(options.buffer_length / mtr->interval); } /* queue reading into sending buffer logging thread if @@ -99,16 +101,16 @@ void * reading_thread(void *arg) { pthread_mutex_unlock(&buf->mutex); /* debugging */ - if (options.verbosity >= 10) { + if (options.verbosity >= log_debug) { char dump[1024]; buffer_dump(buf, dump, 1024); - print(LOG_DEBUG, "Buffer dump: %s (size=%i, keep=%i)", ch, dump, buf->size, buf->keep); + print(log_debug, "Buffer dump: %s (size=%i, keep=%i)", ch, dump, buf->size, buf->keep); } } - if ((options.daemon || options.local) && mtr->type->periodic) { - print(LOG_INFO, "Next reading in %i seconds", mtr, assoc->interval); - sleep(assoc->interval); // TODO handle parsing + if ((options.daemon || options.local) && details->periodic) { + print(log_info, "Next reading in %i seconds", mtr, mtr->interval); + sleep(mtr->interval); } } while (options.daemon || options.local); @@ -124,13 +126,12 @@ void logging_thread_cleanup(void *arg) { void * logging_thread(void *arg) { channel_t *ch = (channel_t *) arg; /* casting argument */ CURL *curl = api_curl_init(ch); - + pthread_cleanup_push(&logging_thread_cleanup, curl); do { /* start thread mainloop */ CURLresponse response; json_object *json_obj; - reading_t *last; const char *json_str; long int http_code, curl_code; @@ -145,11 +146,12 @@ void * logging_thread(void *arg) { } pthread_mutex_unlock(&ch->buffer.mutex); - last = ch->buffer.tail; - json_obj = api_json_tuples(&ch->buffer, ch->buffer.sent, last); + reading_t *first = ch->buffer.sent; + reading_t *last = ch->buffer.tail; + json_obj = api_json_tuples(&ch->buffer, first, last); json_str = json_object_to_json_string(json_obj); - print(10, "JSON request body: %s", ch, json_str); + print(log_debug, "JSON request body: %s", ch, json_str); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_str); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_custom_write_callback); @@ -160,17 +162,17 @@ void * logging_thread(void *arg) { /* check response */ if (curl_code == CURLE_OK && http_code == 200) { /* everything is ok */ - print(4, "Request succeeded with code: %i", ch, http_code); + print(log_debug, "Request succeeded with code: %i", ch, http_code); ch->buffer.sent = last->next; } else { /* error */ if (curl_code != CURLE_OK) { - print(-1, "CURL: %s", ch, curl_easy_strerror(curl_code)); + print(log_error, "CURL: %s", ch, curl_easy_strerror(curl_code)); } else if (http_code != 200) { char err[255]; api_parse_exception(response, err, 255); - print(-1, "Error from middleware: %s", ch, err); + print(log_error, "Error from middleware: %s", ch, err); } } @@ -179,7 +181,7 @@ void * logging_thread(void *arg) { json_object_put(json_obj); if (options.daemon && (curl_code != CURLE_OK || http_code != 200)) { - print(1, "Waiting %i secs for next request due to previous failure", ch, options.retry_pause); + print(log_info, "Waiting %i secs for next request due to previous failure", ch, options.retry_pause); sleep(options.retry_pause); } } while (options.daemon); diff --git a/bin/logger/src/vzlogger.c b/bin/logger/src/vzlogger.c index 25f9476..0d9f7ea 100644 --- a/bin/logger/src/vzlogger.c +++ b/bin/logger/src/vzlogger.c @@ -23,10 +23,9 @@ * along with volkszaehler.org. If not, see . */ - -#include /* for print() */ +#include +#include #include -#include #include #include #include @@ -36,12 +35,15 @@ #include #include #include +#include #include +#include +#include +#include + #include "vzlogger.h" -#include "list.h" #include "channel.h" -#include "configuration.h" #include "threads.h" #ifdef LOCAL_SUPPORT @@ -49,10 +51,8 @@ #include "local.h" #endif /* LOCAL_SUPPORT */ -extern const meter_type_t meter_types[]; - -list_t assocs; /* mapping between meters and channels */ -options_t options; /* global application options */ +list_t mappings; /* mapping between meters and channels */ +config_options_t options; /* global application options */ /** * Command line options @@ -91,79 +91,94 @@ const char *long_options_descs[] = { }; /** - * Print available options and some other usefull information + * Print error/debug/info messages to stdout and/or logfile + * + * @param id could be NULL for general messages + * @todo integrate into syslog */ -void usage(char *argv[]) { +void print(int level, const char *format, void *id, ... ) { + va_list args; + + if (level > options.verbosity) { + return; /* skip message if its under the verbosity level */ + } + + struct timeval now; + struct tm * timeinfo; + char buffer[1024]; + char *pos = buffer; + + gettimeofday(&now, NULL); + timeinfo = localtime(&now.tv_sec); + + /* print timestamp to buffer */ + pos += sprintf(pos, "["); + pos += strftime(pos, 16, "%b %d %H:%M:%S", timeinfo); + + /* print logging 'section' */ + pos += (id != NULL) ? sprintf(pos, "][%s]", (char *) id) : sprintf(pos, "]"); + + /* fill with whitespaces */ + while(pos - buffer < 24) { + pos += sprintf(pos, " "); + } + + /* print formatstring */ + va_start(args, id); + pos += vsprintf(pos, format, args); + va_end(args); + + /* print to stdout/stderr */ + fprintf((level > 0) ? stdout : stderr, "%s\n", buffer); + + /* append to logfile */ + if (options.logfd) { + fprintf(options.logfd, "%s\n", buffer); + fflush(options.logfd); + } +} + +/** + * Print available options, protocols and OBIS aliases + */ +void show_usage(char *argv[]) { const char **desc = long_options_descs; const struct option *op = long_options; - const meter_type_t *type = meter_types; printf("Usage: %s [options]\n\n", argv[0]); - printf(" following options are available:\n"); + /* command line options */ + printf(" following options are available:\n"); while (op->name && desc) { printf("\t-%c, --%-12s\t%s\n", op->val, op->name, *desc); op++; desc++; } - printf("\n"); - printf(" following protocol types are supported:\n"); - - while (type->name) { - printf("\t%-12s\t%s\n", type->name, type->desc); - type++; + /* protocols */ + printf("\n following protocol types are supported:\n"); + for (const meter_details_t *it = meter_get_protocols(); it->name != NULL; it++) { + printf("\t%-12s\t%s\n", it->name, it->desc); } + /* obis aliases */ + printf("\n following OBIS aliases are available:\n"); + char obis_str[OBIS_STR_LEN]; + for (const obis_alias_t *it = obis_get_aliases(); it->name != NULL; it++) { + obis_unparse(it->id, obis_str, OBIS_STR_LEN); + printf("\t%-17s%-31s%-22s\n", it->name, it->desc, obis_str); + } + + /* footer */ printf("\n%s - volkszaehler.org logging utility\n", PACKAGE_STRING); printf("by Steffen Vogel \n"); printf("send bugreports to %s\n", PACKAGE_BUGREPORT); } /** - * Wrapper to log notices and errors + * Fork process to background * - * @param ch could be NULL for general messages - * @todo integrate into syslog + * @link http://www.enderunix.org/docs/eng/daemon.php */ -void print(int level, const char *format, void *id, ... ) { - va_list args; - - struct timeval now; - struct tm * timeinfo; - char buffer[1024], *pos = buffer; - - if (level <= options.verbosity) { - gettimeofday(&now, NULL); - timeinfo = localtime(&now.tv_sec); - - pos += sprintf(pos, "["); - pos += strftime(pos, 16, "%b %d %H:%M:%S", timeinfo); - - if (id != NULL) { - pos += sprintf(pos, "][%s]", (char *) id); - } - else { - pos += sprintf(pos, "]"); - } - - while(pos - buffer < 24) { - pos += sprintf(pos, " "); - } - - va_start(args, id); - pos += vsprintf(pos, format, args); - va_end(args); - - fprintf((level > 0) ? stdout : stderr, "%s\n", buffer); - - if (options.logfd) { - fprintf(options.logfd, "%s\n", buffer); - fflush(options.logfd); - } - } -} - -/* http://www.enderunix.org/docs/eng/daemon.php */ void daemonize() { if(getppid() == 1) { return; /* already a daemon */ @@ -181,7 +196,7 @@ void daemonize() { setsid(); /* obtain a new process group */ - for (i=getdtablesize();i>=0;--i) { + for (i = getdtablesize(); i >= 0; --i) { close(i); /* close all descriptors */ } @@ -211,15 +226,12 @@ void daemonize() { * Threads gets joined in main() */ void quit(int sig) { - print(2, "Closing connections to terminate", NULL); + print(log_info, "Closing connections to terminate", NULL); - foreach(assocs, it) { - assoc_t *assoc = (assoc_t *) it->data; + foreach(mappings, mapping, map_t) { + pthread_cancel(mapping->thread); - pthread_cancel(assoc->thread); - - foreach(assoc->channels, it) { - channel_t *ch = (channel_t *) it->data; + foreach(mapping->channels, ch, channel_t) { pthread_cancel(ch->thread); } } @@ -227,8 +239,11 @@ void quit(int sig) { /** * Parse options from command line + * + * @param options pointer to structure for options + * @return int 0 on succes, <0 on error */ -void parse_options(int argc, char * argv[], options_t * options) { +int config_parse_cli(int argc, char * argv[], config_options_t * options) { while (1) { int c = getopt_long(argc, argv, "c:o:p:lhVdfv:", long_options, NULL); @@ -274,10 +289,17 @@ void parse_options(int argc, char * argv[], options_t * options) { case '?': case 'h': default: - usage(argv); - exit((c == '?') ? EXIT_FAILURE : EXIT_SUCCESS); + show_usage(argv); + if (c == '?') { + exit(EXIT_FAILURE); + } + else { + exit(EXIT_SUCCESS); + } } } + + return SUCCESS; } /** @@ -307,19 +329,24 @@ int main(int argc, char *argv[]) { sigaction(SIGHUP, &action, NULL); /* catch hangup signal */ sigaction(SIGTERM, &action, NULL); /* catch kill signal */ - /* initialize adts and apis */ + /* initialize ADTs and APIs */ curl_global_init(CURL_GLOBAL_ALL); - list_init(&assocs); + list_init(&mappings); /* parse command line and file options */ // TODO command line should have a higher priority as file - parse_options(argc, argv, &options); - parse_configuration(options.config, &assocs, &options); + if (config_parse_cli(argc, argv, &options) != SUCCESS) { + return EXIT_FAILURE; + } + + if (config_parse(options.config, &mappings, &options) != SUCCESS) { + return EXIT_FAILURE; + } options.logging = (!options.local || options.daemon); if (!options.foreground && (options.daemon || options.local)) { - print(1, "Daemonize process...", NULL); + print(log_info, "Daemonize process...", NULL); daemonize(); } @@ -327,46 +354,44 @@ int main(int argc, char *argv[]) { if (options.log) { FILE *logfd = fopen(options.log, "a"); - if (logfd) { - options.logfd = logfd; - print(LOG_DEBUG, "Opened logfile %s", NULL, options.log); - } - else { - print(LOG_ERROR, "Cannot open logfile %s: %s", NULL, options.log, strerror(errno)); - exit(EXIT_FAILURE); + if (logfd == NULL) { + print(log_error, "Cannot open logfile %s: %s", NULL, options.log, strerror(errno)); + return EXIT_FAILURE; } + + options.logfd = logfd; + print(log_debug, "Opened logfile %s", NULL, options.log); } - if (assocs.size == 0) { - print(6, "No meters found!", NULL); - exit(EXIT_FAILURE); + if (mappings.size <= 0) { + print(log_error, "No meters found!", NULL); + return EXIT_FAILURE; } /* open connection meters & start threads */ - foreach(assocs, it) { - assoc_t *assoc = (assoc_t *) it->data; - meter_t *mtr = &assoc->meter; + foreach(mappings, mapping, map_t) { + meter_t *mtr = &mapping->meter; - int res = meter_open(mtr); - if (res < 0) { - print(LOG_ERROR, "Failed to open meter", mtr); - exit(EXIT_FAILURE); + if (meter_open(mtr) != SUCCESS) { + print(log_error, "Failed to open meter. Aborting.", mtr); + return EXIT_FAILURE; + } + else { + print(log_info, "Meter connection established", mtr); } - print(5, "Meter connected", mtr); - pthread_create(&assoc->thread, NULL, &reading_thread, (void *) assoc); - print(5, "Meter thread started", mtr); - foreach(assoc->channels, it) { - channel_t *ch = (channel_t *) it->data; + pthread_create(&mapping->thread, NULL, &reading_thread, (void *) mapping); + print(log_debug, "Meter thread started", mtr); + foreach(mapping->channels, ch, channel_t) { /* set buffer length for perriodic meters */ - if (mtr->type->periodic && options.local) { - ch->buffer.keep = ceil(options.buffer_length / (double) assoc->interval); + if (meter_get_details(mtr->protocol)->periodic && options.local) { + ch->buffer.keep = ceil(options.buffer_length / (double) mapping->meter.interval); } - if (ch->status != RUNNING && options.logging) { + if (ch->status != status_running && options.logging) { pthread_create(&ch->thread, NULL, &logging_thread, (void *) ch); - print(5, "Logging thread started", ch); + print(log_debug, "Logging thread started", ch); } } } @@ -375,49 +400,46 @@ int main(int argc, char *argv[]) { /* start webserver for local interface */ struct MHD_Daemon *httpd_handle = NULL; if (options.local) { - print(5, "Starting local interface HTTPd on port %i", "http", options.port); + print(log_info, "Starting local interface HTTPd on port %i", "http", options.port); httpd_handle = MHD_start_daemon( MHD_USE_THREAD_PER_CONNECTION, options.port, NULL, NULL, - &handle_request, &assocs, + &handle_request, &mappings, MHD_OPTION_END ); } #endif /* LOCAL_SUPPORT */ /* wait for all threads to terminate */ - foreach(assocs, it) { - assoc_t *assoc = (assoc_t *) it->data; - meter_t *mtr = &assoc->meter; + foreach(mappings, mapping, map_t) { + meter_t *mtr = &mapping->meter; - pthread_join(assoc->thread, NULL); - - foreach(assoc->channels, it) { - channel_t *ch = (channel_t *) it->data; + pthread_join(mapping->thread, NULL); + foreach(mapping->channels, ch, channel_t) { pthread_join(ch->thread, NULL); channel_free(ch); } - list_free(&assoc->channels); + list_free(&mapping->channels); meter_close(mtr); /* closing connection */ - meter_free(mtr); } #ifdef LOCAL_SUPPORT /* stop webserver */ if (httpd_handle) { - print(8, "Stopping local interface HTTPd", "http"); MHD_stop_daemon(httpd_handle); } #endif /* LOCAL_SUPPORT */ /* householding */ - list_free(&assocs); + list_free(&mappings); curl_global_cleanup(); + + /* close logfile */ if (options.logfd) { fclose(options.logfd); } diff --git a/include/common.h b/include/common.h new file mode 100644 index 0000000..a24d5d3 --- /dev/null +++ b/include/common.h @@ -0,0 +1,35 @@ +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include + +/* enumerations */ +typedef enum { + log_error = -1, + log_warning = 0, + log_info = 5, + log_debug = 10, + log_finest = 15 +} log_level_t; + +/* types */ +typedef unsigned char bool; + +/* constants */ +#ifndef TRUE +# define TRUE 1 +#endif + +#ifndef FALSE +# define FALSE 0 +#endif + +#define SUCCESS 0 +#define ERR -1 +#define ERR_NOT_FOUND -2 +#define ERR_INVALID_TYPE -3 + +/* prototypes */ +void print(log_level_t lvl, const char *format, void *id, ... ); + +#endif /* _COMMON_H_ */ diff --git a/include/d0.h b/include/d0.h index 17f9b55..039737f 100644 --- a/include/d0.h +++ b/include/d0.h @@ -34,15 +34,21 @@ #include typedef struct { + char *host; + char *device; + int baudrate; + int fd; /* file descriptor of port */ struct termios oldtio; /* required to reset port */ } meter_handle_d0_t; -struct meter; /* forward declaration */ -struct reading; /* forward declaration */ +/* forward declarations */ +struct meter; +struct reading; +int meter_init_d0(struct meter *mtr, list_t options); int meter_open_d0(struct meter *mtr); -void meter_close_d0(struct meter *mtr); +int meter_close_d0(struct meter *mtr); size_t meter_read_d0(struct meter *mtr, struct reading *rds, size_t n); int meter_d0_parse_line(struct reading *rd, char *line, size_t n); diff --git a/include/exec.h b/include/exec.h new file mode 100644 index 0000000..e037b22 --- /dev/null +++ b/include/exec.h @@ -0,0 +1,45 @@ +/** + * Get data by calling programs + * + * @package vzlogger + * @copyright Copyright (c) 2011, The volkszaehler.org project + * @license http://www.gnu.org/licenses/gpl.txt GNU Public License + * @author Steffen Vogel + */ +/* + * This file is part of volkzaehler.org + * + * volkzaehler.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * volkzaehler.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with volkszaehler.org. If not, see . + */ + +#ifndef _EXEC_H_ +#define _EXEC_H_ + +#include + +typedef struct { + char *command; + char *regex; +} meter_handle_exec_t; + +/* forward declarations */ +struct meter; +struct reading; + +int meter_init_exec(struct meter *mtr, list_t options); +int meter_open_exec(struct meter *mtr); +int meter_close_exec(struct meter *mtr); +size_t meter_read_exec(struct meter *mtr, struct reading *rds, size_t n); + +#endif /* _EXEC_H_ */ diff --git a/include/file.h b/include/file.h new file mode 100644 index 0000000..64c12cf --- /dev/null +++ b/include/file.h @@ -0,0 +1,47 @@ +/** + * Read data from files & fifos + * + * @package vzlogger + * @copyright Copyright (c) 2011, The volkszaehler.org project + * @license http://www.gnu.org/licenses/gpl.txt GNU Public License + * @author Steffen Vogel + */ +/* + * This file is part of volkzaehler.org + * + * volkzaehler.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * volkzaehler.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with volkszaehler.org. If not, see . + */ + +#ifndef _FILE_H_ +#define _FILE_H_ + +#include + +typedef struct { + char *path; + char *regex; + + FILE *fd; +} meter_handle_file_t; + +/* forward declarations */ +struct meter; +struct reading; + +int meter_init_file(struct meter *mtr, list_t options); +int meter_open_file(struct meter *mtr); +int meter_close_file(struct meter *mtr); +size_t meter_read_file(struct meter *mtr, struct reading *rds, size_t n); + +#endif /* _FILE_H_ */ diff --git a/include/list.h b/include/list.h new file mode 100644 index 0000000..02f1261 --- /dev/null +++ b/include/list.h @@ -0,0 +1,116 @@ +/** + * Generic linked list + * + * @package vzlogger + * @copyright Copyright (c) 2011, The volkszaehler.org project + * @license http://www.gnu.org/licenses/gpl.txt GNU Public License + * @author Steffen Vogel + */ +/* + * This file is part of volkzaehler.org + * + * volkzaehler.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * volkzaehler.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with volkszaehler.org. If not, see . + */ + +#ifndef _LIST_H_ +#define _LIST_H_ + +#include + +#define CONCAT2(a, b) a ## b +#define CONCAT(a, b) CONCAT2(a, b) +#define UNIQUE(prefix) CONCAT(__ ## prefix ## _, __LINE__ ) + +#define foreach(list, value, type) \ + __list_item_t *UNIQUE(it) = (list).head; \ + for( \ + type * (value) = UNIQUE(it)->data; \ + ({ \ + if (UNIQUE(it)) { \ + (value) = UNIQUE(it)->data; \ + } \ + ; UNIQUE(it) != NULL; \ + }); \ + UNIQUE(it) = UNIQUE(it)->next \ + ) \ + +typedef struct __list_item { + void *data; + struct __list_item *prev; + struct __list_item *next; +} __list_item_t; + +typedef struct { + size_t size; + __list_item_t *head; + __list_item_t *tail; +} list_t; + +inline void list_init(list_t *list) { + list->size = 0; + list->head = list->tail = NULL; +} + +inline size_t list_push(list_t *list, void *data) { + __list_item_t *new = malloc(sizeof(__list_item_t)); + + if (new == NULL) return -1; /* cannot allocate memory */ + + new->data = data; + new->prev = list->tail; + new->next = NULL; + + if (list->tail == NULL) { + list->head = new; + } + else { + list->tail->next = new; + } + + list->tail = new; + list->size = list->size + 1; + + return list->size; +} + +inline void * list_pop(list_t *list) { + __list_item_t *old = list->tail; + + if (old == NULL) { + return NULL; + } + + void *data = old->data; + + list->tail = old->prev; + list->size--; + + free(old); + + return data; +} + +inline void list_free(list_t *list) { + while (list->head != NULL) { + __list_item_t *old = list->head; + list->head = old->next; + + free(old->data); + free(old); + } + + list_init(list); +} + +#endif /* _LIST_H_ */ diff --git a/include/meter.h b/include/meter.h index 2b078b9..e4d92e8 100644 --- a/include/meter.h +++ b/include/meter.h @@ -36,64 +36,77 @@ * The 'interval' column in the configuration as no meaning. */ -#include -#include +#include -#include "config.h" +#include "common.h" +#include "list.h" #include "obis.h" /* meter types */ +#include "file.h" +#include "exec.h" #include "random.h" #include "s0.h" #include "d0.h" -#include "onewire.h" #ifdef SML_SUPPORT #include "sml.h" #endif /* SML_SUPPORT */ typedef union reading_id { obis_id_t obis; + /* char *string; */ + /* char *uuid; */ } reading_id_t; typedef struct reading { float value; struct timeval time; - union reading_id identifier; - + reading_id_t identifier; + struct reading *next; /* pointer for linked list */ } reading_t; -typedef struct { - enum { - RANDOM, - S0, - D0, - ONEWIRE, - SML - } id; - const char *name; /* short identifier for protocol */ - const char *desc; /* more detailed description */ - size_t max_readings; /* how many readings can be read with 1 call */ - int periodic:1; /* does this meter has be triggered periodically? */ -} meter_type_t; +typedef enum { + meter_protocol_file = 1, + meter_protocol_exec, + meter_protocol_random, + meter_protocol_s0, + meter_protocol_d0, + meter_protocol_sml +} meter_protocol_t; typedef struct meter { - char id[5]; /* only for internal usage & debugging */ - char *connection; /* args for connection, further configuration etc */ - const meter_type_t *type; - + char id[5]; + meter_protocol_t protocol; + int interval; + union { + meter_handle_file_t file; + meter_handle_exec_t exec; + meter_handle_random_t random; meter_handle_s0_t s0; meter_handle_d0_t d0; - meter_handle_onewire_t onewire; - meter_handle_random_t random; #ifdef SML_SUPPORT meter_handle_sml_t sml; #endif /* SML_SUPPORT */ } handle; } meter_t; -/* Prototypes */ +typedef struct { + meter_protocol_t id; + char *name; /* short identifier for protocol */ + char *desc; /* more detailed description */ + size_t max_readings; /* how many readings can be read with 1 call */ + int periodic:1; /* does this meter has be triggered periodically? */ + + /* function pointers */ + int (*init_func)(meter_t *mtr, list_t options); + int (*open_func)(meter_t *mtr); + int (*close_func)(meter_t *mtr); + size_t (*read_func)(meter_t *mtr, reading_t *rds, size_t n); +} meter_details_t; + +/* prototypes */ /** * Converts timeval structure to double @@ -103,14 +116,23 @@ typedef struct meter { */ double tvtod(struct timeval tv); +/** + * Get list of available meter types + */ +const meter_details_t * meter_get_protocols(); + +const meter_details_t * meter_get_details(meter_protocol_t protocol); + +int meter_lookup_protocol(const char *name, meter_protocol_t *protocol); + /** * Initialize meter * * @param mtr the meter structure to initialze - * @param type the type it should be initialized with - * @param connection type specific initialization arguments (connection settings, port, etc..) + * @param list of key, value pairs of options + * @return 0 on success, -1 on error */ -void meter_init(meter_t *mtr, const meter_type_t *type, const char *connection); +int meter_init(meter_t *mtr, list_t options); /** * Freeing all memory which has been allocated during the initialization @@ -137,6 +159,7 @@ size_t meter_read(meter_t *mtr, reading_t rds[], size_t n); * Establish connection, initialize meter etc. * * @param mtr the meter structure + * @return 0 on success, -1 on error */ int meter_open(meter_t *mtr); @@ -146,7 +169,8 @@ int meter_open(meter_t *mtr); * Reset ports, shutdown meter etc. * * @param mtr the meter structure + * @return 0 on success, -1 on error */ -void meter_close(meter_t *mtr); +int meter_close(meter_t *mtr); #endif /* _METER_H_ */ diff --git a/include/obis.h b/include/obis.h index 9526d5f..e5bf920 100644 --- a/include/obis.h +++ b/include/obis.h @@ -28,64 +28,9 @@ #include -typedef enum { - ABSTRACT = 0, - ELECTRIC = 1, - HEAT_COST = 4, - COOLING = 5, - HEATING = 6, - GAS = 7, - WATER_COLD = 8, - WATER_HOT = 9 -} obis_media_t; +#define OBIS_STR_LEN (6*3+5+1) -typedef union { - enum { - GENERAL_PURPOSE = 0, - ACTIVE_POWER_IN = 1, - ACTIVE_POWER_OUT = 2, - REACTIVE_POWER_IN = 3, - REACTIVE_POWER_OUT = 4, - REACTIVE_POWER_Q1 = 5, - REACTIVE_POWER_Q2 = 6, - REACTIVE_POWER_Q3 = 7, - REACTIVE_POWER_Q4 = 8, - APPARENT_POWER_IN = 9, - APPARENT_POWER_OUT = 10, - CURRENT_ANY = 11, - VOLTAGE_ANY = 12, - POWER_FACTOR_OUT = 13, - SUPPLY_FREQUENCY = 14, - ACTIVE_POWER_Q1 = 17, - ACTIVE_POWER_Q2 = 18, - ACTIVE_POWER_Q3 = 19, - ACTIVE_POWER_Q4 = 20, - - L1_ACTIVE_POWER_INT = 21, - L1_ACTIVE_POWER_OUT = 22, - - ANGLES = 81, - UNITLESS = 82, - LOSS = 83, - - L1_POWER_FACTOR_OUT = 85, - L2_POWER_FACTOR_OUT = 86, - L3_POWER_FACTOR_OUT = 87, - - AMPERE_SQUARE_HOURS = 88, - VOLT_SQUARE_HOURS = 89, - - SERVICE = 96, - ERROR = 97, - LIST = 98, - DATA_PROFILE = 99 - - } electric; - - int gas; /* as defined in DIN EN 13757-1 */ -} obis_indicator_t; - -/* regex: A-BB:CC.DD.EE(*FF)? */ +/* regex: A-BB:CC.DD.EE([*&]FF)? */ typedef union { unsigned char raw[6]; struct { @@ -100,12 +45,15 @@ typedef struct { char *desc; } obis_alias_t; -/* Prototypes */ -obis_id_t * obis_init(obis_id_t *id, const unsigned char *raw); -obis_id_t * obis_lookup_alias(const char *alias); -int obis_parse(obis_id_t *id, const char *str, size_t n); +/* prototypes */ +obis_id_t * obis_init(obis_id_t *id, unsigned char *raw); + +const obis_alias_t * obis_get_aliases(); +int obis_parse(const char *str, obis_id_t *id); +int obis_lookup_alias(const char *alias, obis_id_t *id); int obis_unparse(obis_id_t id, char *buffer, size_t n); int obis_compare(obis_id_t a, obis_id_t b); + int obis_is_manufacturer_specific(obis_id_t id); int obis_is_null(obis_id_t id); diff --git a/include/options.h b/include/options.h new file mode 100644 index 0000000..9c13382 --- /dev/null +++ b/include/options.h @@ -0,0 +1,44 @@ +#ifndef _OPTIONS_H_ +#define _OPTIONS_H_ + +#include "list.h" + +typedef union { + const char *string; + int integer; + double floating; + int boolean:1; +} option_value_t; + +/* subset of json_type's */ +typedef enum { + option_type_boolean = 1, + option_type_double, + option_type_int, + option_type_string = 6 +} option_type_t; + +typedef struct { + char *key; + option_type_t type; + option_value_t value; +} option_t; + +/** + * Lookup option by key in a list of options + * + * @param list_t the list of options + * @param char *key the key you are looking for + * @return int success or error (CFG_* constants) + */ +int options_lookup(list_t options, char *key, void *value, option_type_t type); + +/** + * Type specific wrapper functions for config_lookup_type() + */ +int options_lookup_string(list_t options, char *key, char **value); +int options_lookup_int(list_t options, char *key, int *value); +int options_lookup_double(list_t options, char *key, double *value); +int options_lookup_boolean(list_t options, char *key, int *value); + +#endif /* _OPTIONS_H_ */ diff --git a/include/random.h b/include/random.h index 9eaf7b6..8b04cf6 100644 --- a/include/random.h +++ b/include/random.h @@ -27,15 +27,19 @@ #define _RANDOM_H_ typedef struct { - double min, max, last; + double min, max; + + double last; } meter_handle_random_t; -struct meter; /* forward declaration */ -struct reading; /* forward declaration */ -double ltqnorm(double p); /* forward declaration */ +/* forward declarations */ +struct meter; +struct reading; +double ltqnorm(double p); +int meter_init_random(struct meter *mtr, list_t options); int meter_open_random(struct meter *mtr); -void meter_close_random(struct meter *mtr); +int meter_close_random(struct meter *mtr); size_t meter_read_random(struct meter *mtr, struct reading *rds, size_t n); #endif /* _RANDOM_H_ */ diff --git a/include/s0.h b/include/s0.h index 5b1a3b4..43d42e2 100644 --- a/include/s0.h +++ b/include/s0.h @@ -30,15 +30,19 @@ #include typedef struct { + char *device; + int fd; /* file descriptor of port */ struct termios oldtio; /* required to reset port */ } meter_handle_s0_t; -struct meter; /* forward declaration */ -struct reading; /* forward declaration */ +/* forward declarations */ +struct meter; +struct reading; +int meter_init_s0(struct meter *mtr, list_t options); int meter_open_s0(struct meter *mtr); -void meter_close_s0(struct meter *mtr); +int meter_close_s0(struct meter *mtr); size_t meter_read_s0(struct meter *mtr, struct reading *rds, size_t n); #endif /* _S0_H_ */ diff --git a/include/sml.h b/include/sml.h index 6a3fa76..90e67b7 100644 --- a/include/sml.h +++ b/include/sml.h @@ -38,13 +38,17 @@ #include "obis.h" typedef struct { + char *host; + char *device; + int baudrate; + int fd; - //float counter; /* Zählerstand */ //termios old_tio; } meter_handle_sml_t; -struct meter; /* forward declaration */ -struct reading; /* forward declaration */ +/* forward declarations */ +struct meter; +struct reading; /** * Cast arbitrary sized sml_value to double @@ -54,11 +58,20 @@ struct reading; /* forward declaration */ */ double sml_value_todouble(sml_value *value); +/** + * Initialize meter structure with a list of options + * + * @param mtr the meter structure + * @param options a list of options + * @return 0 on success, <0 on error + */ +int meter_init_sml(struct meter *mtr, list_t options); + /** * Open connection via serial port or socket to meter * * @param mtr the meter structure - * @return 0 on success, -1 on error + * @return 0 on success, <0 on error */ int meter_open_sml(struct meter *mtr); @@ -67,7 +80,7 @@ int meter_open_sml(struct meter *mtr); * * @param mtr the meter structure */ -void meter_close_sml(struct meter *mtr); +int meter_close_sml(struct meter *mtr); /** * Blocking read on meter @@ -94,7 +107,7 @@ void meter_sml_parse(sml_list *list, struct reading *rd); * Open serial port by device * * @param device the device path, usually /dev/ttyS* - * @return file descriptor, -1 on error + * @return file descriptor, <0 on error */ int meter_sml_open_port(const char *device); @@ -103,7 +116,7 @@ int meter_sml_open_port(const char *device); * * @param node the hostname or ASCII encoded IP address * @param the ASCII encoded portnum or service as in /etc/services - * @return file descriptor, -1 on error + * @return file descriptor, <0 on error */ int meter_sml_open_socket(const char *node, const char *service); diff --git a/src/Makefile.am b/src/Makefile.am index f409d52..789e094 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,14 +4,14 @@ AM_LDFLAGS = VERSION = 1:0:0 -lib_LIBRARIES = libmeter.a +lib_LIBRARIES = libmtr.a -libmeter_a_SOURCES = meter.c d0.c s0.c random.c onewire.c ltqnorm.c obis.c -libmeter_a_LDFLAGS = -version-info $(VERSION) +libmtr_a_SOURCES = meter.c d0.c s0.c random.c file.c exec.c ltqnorm.c obis.c options.c +libmtr_a_LDFLAGS = -version-info $(VERSION) # SML support #################################################################### if SML_SUPPORT -libmeter_a_SOURCES += sml.c +libmtr_a_SOURCES += sml.c AM_CFLAGS += $(DEPS_SML_CFLAGS) endif diff --git a/src/Makefile.in b/src/Makefile.in index 9b10ddb..9f74801 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -72,15 +72,16 @@ am__installdirs = "$(DESTDIR)$(libdir)" LIBRARIES = $(lib_LIBRARIES) AR = ar ARFLAGS = cru -libmeter_a_AR = $(AR) $(ARFLAGS) -libmeter_a_LIBADD = -am__libmeter_a_SOURCES_DIST = meter.c d0.c s0.c random.c onewire.c \ - ltqnorm.c obis.c sml.c +libmtr_a_AR = $(AR) $(ARFLAGS) +libmtr_a_LIBADD = +am__libmtr_a_SOURCES_DIST = meter.c d0.c s0.c random.c file.c exec.c \ + ltqnorm.c obis.c options.c sml.c @SML_SUPPORT_TRUE@am__objects_1 = sml.$(OBJEXT) -am_libmeter_a_OBJECTS = meter.$(OBJEXT) d0.$(OBJEXT) s0.$(OBJEXT) \ - random.$(OBJEXT) onewire.$(OBJEXT) ltqnorm.$(OBJEXT) \ - obis.$(OBJEXT) $(am__objects_1) -libmeter_a_OBJECTS = $(am_libmeter_a_OBJECTS) +am_libmtr_a_OBJECTS = meter.$(OBJEXT) d0.$(OBJEXT) s0.$(OBJEXT) \ + random.$(OBJEXT) file.$(OBJEXT) exec.$(OBJEXT) \ + ltqnorm.$(OBJEXT) obis.$(OBJEXT) options.$(OBJEXT) \ + $(am__objects_1) +libmtr_a_OBJECTS = $(am_libmtr_a_OBJECTS) DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__depfiles_maybe = depfiles @@ -89,8 +90,8 @@ COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) CCLD = $(CC) LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ -SOURCES = $(libmeter_a_SOURCES) -DIST_SOURCES = $(am__libmeter_a_SOURCES_DIST) +SOURCES = $(libmtr_a_SOURCES) +DIST_SOURCES = $(am__libmtr_a_SOURCES_DIST) ETAGS = etags CTAGS = ctags DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) @@ -193,10 +194,10 @@ top_srcdir = @top_srcdir@ AM_CFLAGS = -Wall -D_REENTRANT -std=gnu99 $(am__append_2) AM_CPPFLAGS = -I$(top_srcdir)/include AM_LDFLAGS = -lib_LIBRARIES = libmeter.a -libmeter_a_SOURCES = meter.c d0.c s0.c random.c onewire.c ltqnorm.c \ - obis.c $(am__append_1) -libmeter_a_LDFLAGS = -version-info $(VERSION) +lib_LIBRARIES = libmtr.a +libmtr_a_SOURCES = meter.c d0.c s0.c random.c file.c exec.c ltqnorm.c \ + obis.c options.c $(am__append_1) +libmtr_a_LDFLAGS = -version-info $(VERSION) all: all-am .SUFFIXES: @@ -263,10 +264,10 @@ uninstall-libLIBRARIES: clean-libLIBRARIES: -test -z "$(lib_LIBRARIES)" || rm -f $(lib_LIBRARIES) -libmeter.a: $(libmeter_a_OBJECTS) $(libmeter_a_DEPENDENCIES) - -rm -f libmeter.a - $(libmeter_a_AR) libmeter.a $(libmeter_a_OBJECTS) $(libmeter_a_LIBADD) - $(RANLIB) libmeter.a +libmtr.a: $(libmtr_a_OBJECTS) $(libmtr_a_DEPENDENCIES) + -rm -f libmtr.a + $(libmtr_a_AR) libmtr.a $(libmtr_a_OBJECTS) $(libmtr_a_LIBADD) + $(RANLIB) libmtr.a mostlyclean-compile: -rm -f *.$(OBJEXT) @@ -275,10 +276,12 @@ distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d0.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exec.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ltqnorm.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/meter.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/obis.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/onewire.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/options.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/random.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/s0.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sml.Po@am__quote@ diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..e69de29 diff --git a/src/d0.c b/src/d0.c index a930a88..afdff39 100644 --- a/src/d0.c +++ b/src/d0.c @@ -35,6 +35,7 @@ #include #include #include +#include /* socket */ #include @@ -42,8 +43,9 @@ #include #include "meter.h" -#include "obis.h" #include "d0.h" +#include "obis.h" +#include "options.h" int meter_d0_open_socket(const char *node, const char *service) { struct sockaddr_in sin; @@ -52,8 +54,8 @@ int meter_d0_open_socket(const char *node, const char *service) { fd = socket(PF_INET, SOCK_STREAM, 0); if (fd < 0) { - fprintf(stderr, "error: socket(): %s\n", strerror(errno)); - return -1; + print(log_error, "socket(): %s", NULL, strerror(errno)); + return ERR; } getaddrinfo(node, service, NULL, &ais); @@ -62,36 +64,56 @@ int meter_d0_open_socket(const char *node, const char *service) { res = connect(fd, (struct sockaddr *) &sin, sizeof(sin)); if (res < 0) { - fprintf(stderr, "error: connect(%s, %s): %s\n", node, service, strerror(errno)); - return -1; + print(log_error, "connect(%s, %s): %s", NULL, node, service, strerror(errno)); + return ERR; } return fd; } +int meter_init_d0(meter_t *mtr, list_t options) { + meter_handle_d0_t *handle = &mtr->handle.d0; + + /* connection */ + handle->host = NULL; + handle->device = NULL; + if (options_lookup_string(options, "host", &handle->host) != SUCCESS && options_lookup_string(options, "device", &handle->device) != SUCCESS) { + print(log_error, "Missing host or port", mtr); + return ERR; + } + + /* baudrate */ + handle->baudrate = 9600; + if (options_lookup_int(options, "baudrate", &handle->baudrate) == ERR_INVALID_TYPE) { + print(log_error, "Invalid type for baudrate", mtr); + return ERR; + } + + return SUCCESS; +} int meter_open_d0(meter_t *mtr) { meter_handle_d0_t *handle = &mtr->handle.d0; - char *addr = strdup(mtr->connection); - char *node = strsep(&addr, ":"); - char *service = strsep(&addr, ":"); + if (handle->device != NULL) { + print(log_error, "TODO: implement serial interface", mtr); + return ERR; + } + else if (handle->host != NULL) { + char *addr = strdup(handle->host); + char *node = strsep(&addr, ":"); + char *service = strsep(&addr, ":"); - printf("socket: %s %s\n", node, service); + handle->fd = meter_d0_open_socket(node, service); + } - handle->fd = meter_d0_open_socket(node, service); - - free(addr); - - printf("socket opened: %s %s\n", node, service); - - return (handle->fd < 0) ? -1 : 0; + return (handle->fd < 0) ? ERR : SUCCESS; } -void meter_close_d0(meter_t *mtr) { +int meter_close_d0(meter_t *mtr) { meter_handle_d0_t *handle = &mtr->handle.d0; - close(handle->fd); + return close(handle->fd); } size_t meter_read_d0(meter_t *mtr, reading_t rds[], size_t n) { @@ -108,7 +130,7 @@ size_t meter_read_d0(meter_t *mtr, reading_t rds[], size_t n) { char baudrate; /* 1 byte */ char byte; int j, k, m; - + j = k = m = baudrate = 0; context = START; @@ -122,7 +144,6 @@ size_t meter_read_d0(meter_t *mtr, reading_t rds[], size_t n) { if (byte == '/') { j = k = m = 0; context = VENDOR; - printf("reset!!!\n"); } break; @@ -145,7 +166,7 @@ size_t meter_read_d0(meter_t *mtr, reading_t rds[], size_t n) { break; case IDENT: - /* Data block starts after twice a '\r\n' sequence */ + /* data block starts after twice a '\r\n' sequence */ /* b= CR LF CR LF */ /* k= 1 2 3 4 */ if (byte == '\r' || byte == '\n') { @@ -202,9 +223,9 @@ size_t meter_read_d0(meter_t *mtr, reading_t rds[], size_t n) { k++; if (k >= 2) { if (m < n) { /* free slots available? */ - printf("parsed reading (id=%s, value=%s, unit=%s)\n", id, value, unit); + //printf("parsed reading (id=%s, value=%s, unit=%s)\n", id, value, unit); rds[m].value = strtof(value, NULL); - obis_parse(&rds[m].identifier.obis, id, strlen(id)); + obis_parse(id, &rds[m].identifier.obis); gettimeofday(&rds[m].time, NULL); j = k = 0; @@ -217,13 +238,13 @@ size_t meter_read_d0(meter_t *mtr, reading_t rds[], size_t n) { break; case END: - printf("read package with %i tuples (vendor=%s, baudrate=%c, ident=%s)\n", m, vendor, baudrate, identification); + print(log_info, "Read package with %i tuples (vendor=%s, baudrate=%c, ident=%s)", mtr, m, vendor, baudrate, identification); return m; } } error: - printf("something unexpected happened: %s:%i!\n", __FUNCTION__, __LINE__); + print(log_error, "Something unexpected happened: %s:%i!", mtr, __FUNCTION__, __LINE__); return 0; } diff --git a/src/exec.c b/src/exec.c new file mode 100644 index 0000000..820b8d7 --- /dev/null +++ b/src/exec.c @@ -0,0 +1,69 @@ +/** + * Get data by calling programs + * + * @package vzlogger + * @copyright Copyright (c) 2011, The volkszaehler.org project + * @license http://www.gnu.org/licenses/gpl.txt GNU Public License + * @author Steffen Vogel + */ +/* + * This file is part of volkzaehler.org + * + * volkzaehler.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * volkzaehler.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with volkszaehler.org. If not, see . + */ + +#include +#include + +#include "meter.h" +#include "exec.h" +#include "options.h" + +int meter_init_exec(meter_t *mtr, list_t options) { + meter_handle_exec_t *handle = &mtr->handle.exec; + + if (options_lookup_string(options, "command", &handle->command) != SUCCESS) { + print(log_error, "Missing command or invalid type", mtr); + return ERR; + } + + handle->regex = NULL; + if (options_lookup_string(options, "regex", &handle->regex) == ERR_INVALID_TYPE) { + print(log_error, "Regex has to be a string", mtr); + return ERR; + } + + return SUCCESS; +} + +int meter_open_exec(meter_t *mtr) { + //meter_handle_exec_t *handle = &mtr->handle.exec; + + // TODO implement + return ERR; +} + +int meter_close_exec(meter_t *mtr) { + //meter_handle_exec_t *handle = &mtr->handle.exec; + + // TODO implement + return ERR; +} + +size_t meter_read_exec(meter_t *mtr, reading_t rds[], size_t n) { + //meter_handle_exec_t *handle = &mtr->handle.exec; + + // TODO implement + return 0; +} diff --git a/src/file.c b/src/file.c new file mode 100644 index 0000000..ab8a3e6 --- /dev/null +++ b/src/file.c @@ -0,0 +1,95 @@ +/** + * Read data from files & fifos + * + * @package vzlogger + * @copyright Copyright (c) 2011, The volkszaehler.org project + * @license http://www.gnu.org/licenses/gpl.txt GNU Public License + * @author Steffen Vogel + */ +/* + * This file is part of volkzaehler.org + * + * volkzaehler.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * volkzaehler.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with volkszaehler.org. If not, see . + */ + +#include +#include +#include + +#include "meter.h" +#include "file.h" +#include "options.h" + +int meter_init_file(meter_t *mtr, list_t options) { + meter_handle_file_t *handle = &mtr->handle.file; + + char *path; + if (options_lookup_string(options, "path", &path) == SUCCESS) { + handle->path = strdup(path); + } + else { + print(log_error, "Missing path or invalid type", mtr); + return ERR; + } + + char *regex; + if (options_lookup_string(options, "regex", ®ex) == SUCCESS) { + handle->regex = strdup(regex); + } + else if (options_lookup_string(options, "regex", ®ex) == ERR_INVALID_TYPE) { // TODO improve code + print(log_error, "Regex has to be a string", mtr); + return ERR; + } + + return SUCCESS; +} + +int meter_open_file(meter_t *mtr) { + meter_handle_file_t *handle = &mtr->handle.file; + + handle->fd = fopen(handle->path, "r"); + + if (handle->fd == NULL) { + print(log_error, "fopen(%s): %s", mtr, handle->path, strerror(errno)); + return ERR; + } + + return SUCCESS; +} + +int meter_close_file(meter_t *mtr) { + meter_handle_file_t *handle = &mtr->handle.file; + + return fclose(handle->fd); +} + +size_t meter_read_file(meter_t *mtr, reading_t rds[], size_t n) { + meter_handle_file_t *handle = &mtr->handle.file; + + char buffer[16]; + int bytes; + + rewind(handle->fd); + bytes = fread(buffer, 1, 16, handle->fd); + buffer[bytes] = '\0'; /* zero terminated, required? */ + + if (bytes) { + rds->value = strtof(buffer, NULL); + gettimeofday(&rds->time, NULL); + + return 1; + } + + return 0; /* empty file */ +} diff --git a/src/ltqnorm.c b/src/ltqnorm.c index 283d6bb..bd0bf64 100644 --- a/src/ltqnorm.c +++ b/src/ltqnorm.c @@ -19,7 +19,7 @@ #include #include -/* Coefficients in rational approximations. */ +/* coefficients in rational approximations. */ static const double a[] = { -3.969683028665376e+01, 2.209460984245205e+02, @@ -74,17 +74,17 @@ double ltqnorm(double p) { errno = ERANGE; return HUGE_VAL /* "infinity" */; } - else if (p < LOW) { /* Rational approximation for lower region */ + else if (p < LOW) { /* rational approximation for lower region */ q = sqrt(-2*log(p)); return (((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) / ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1); } - else if (p > HIGH) { /* Rational approximation for upper region */ + else if (p > HIGH) { /* rational approximation for upper region */ q = sqrt(-2*log(1-p)); return -(((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) / ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1); } - else { /* Rational approximation for central region */ + else { /* rational approximation for central region */ q = p - 0.5; r = q*q; return (((((a[0]*r+a[1])*r+a[2])*r+a[3])*r+a[4])*r+a[5])*q / diff --git a/src/meter.c b/src/meter.c index 8706970..25de90d 100644 --- a/src/meter.c +++ b/src/meter.c @@ -25,79 +25,93 @@ #include #include -#include - -#include "../bin/logger/include/list.h" +#include #include "meter.h" +#include "options.h" -/* List of available meter types */ -const meter_type_t meter_types[] = { - {ONEWIRE, "onewire", "Dallas 1-Wire sensors (via OWFS)", 1, 1}, - {RANDOM, "random", "Random walk", 1, 1}, - {S0, "s0", "S0 on RS232", 1, 1}, - {D0, "d0", "On-site plaintext protocol (DIN EN 62056-21)", 16, 50}, +#define METER_DETAIL(NAME, DESC, MAX_RDS, PERIODIC) { meter_protocol_##NAME, #NAME, DESC, MAX_RDS, PERIODIC, meter_init_##NAME, meter_open_##NAME, meter_close_##NAME, meter_read_##NAME } + +static const meter_details_t protocols[] = { +/* alias description max_rds periodic +===============================================================================================*/ +METER_DETAIL(file, "Read from file (ex. 1-Wire sensors via OWFS)", 1, TRUE), +METER_DETAIL(exec, "Read from program (ex. 1-Wire sensors via digitemp)", 1, TRUE), +METER_DETAIL(random, "Random walk", 1, TRUE), +METER_DETAIL(s0, "S0 on RS232", 1, TRUE), +METER_DETAIL(d0, "Plaintext protocol (DIN EN 62056-21)", 32, FALSE), #ifdef SML_SUPPORT - {SML, "sml", "Smart Meter Language", 16, 0}, +METER_DETAIL(sml, "Smart Meter Language", 32, FALSE), #endif /* SML_SUPPORT */ - {} /* stop condition for iterator */ +{} /* stop condition for iterator */ }; +int meter_init(meter_t *mtr, list_t options) { + static int instances; /* static to generate unique channel ids */ + snprintf(mtr->id, 5, "mtr%i", instances++); /* set/increment id */ + + /* protocol */ + char *protocol_str; + if (options_lookup_string(options, "protocol", &protocol_str) != SUCCESS) { + print(log_error, "Missing protocol or invalid type", mtr); + return ERR; + } + + if (meter_lookup_protocol(protocol_str, &mtr->protocol) != SUCCESS) { + print(log_error, "Invalid protocol: %s", mtr, protocol_str); + return ERR; /* skipping this meter */ + } + + /* interval */ + mtr->interval = 10; + if (options_lookup_int(options, "interval", &mtr->interval) == ERR_INVALID_TYPE) { + print(log_error, "Invalid type for interval", mtr); + return ERR; + } + + const meter_details_t *details = meter_get_details(mtr->protocol); + return details->init_func(mtr, options); +} + +int meter_open(meter_t *mtr) { + const meter_details_t *details = meter_get_details(mtr->protocol); + return details->open_func(mtr); +} + +int meter_close(meter_t *mtr) { + const meter_details_t *details = meter_get_details(mtr->protocol); + return details->close_func(mtr); +} + +size_t meter_read(meter_t *mtr, reading_t rds[], size_t n) { + const meter_details_t *details = meter_get_details(mtr->protocol); + return details->read_func(mtr, rds, n); +} + +const meter_details_t * meter_get_protocols() { + return protocols; +} + +int meter_lookup_protocol(const char *name, meter_protocol_t *protocol) { + for (const meter_details_t *it = protocols; it != NULL; it++) { + if (strcmp(it->name, name) == 0) { + *protocol = it->id; + return SUCCESS; + } + } + return ERR_NOT_FOUND; +} + +const meter_details_t * meter_get_details(meter_protocol_t protocol) { + for (const meter_details_t *it = protocols; it != NULL; it++) { + if (it->id == protocol) { + return it; + } + } + return NULL; +} + double tvtod(struct timeval tv) { return tv.tv_sec + tv.tv_usec / 1e6; } -void meter_init(meter_t *mtr, const meter_type_t *type, const char *connection) { - static int instances; /* static to generate channel ids */ - snprintf(mtr->id, 5, "mtr%i", instances++); - - mtr->type = type; - mtr->connection = strdup(connection); -} - -void meter_free(meter_t *mtr) { - free(mtr->connection); -} - -int meter_open(meter_t *mtr) { - switch (mtr->type->id) { - case RANDOM: return meter_open_random(mtr); - case S0: return meter_open_s0(mtr); - case D0: return meter_open_d0(mtr); - case ONEWIRE: return meter_open_onewire(mtr); -#ifdef SML_SUPPORT - case SML: return meter_open_sml(mtr); -#endif /* SML_SUPPORT */ - default: fprintf(stderr, "error: unknown meter type: %i\n", mtr->type->id); - } - - return -1; -} - -void meter_close(meter_t *mtr) { - switch (mtr->type->id) { - case RANDOM: meter_close_random(mtr); break; - case S0: meter_close_s0(mtr); break; - case D0: meter_close_d0(mtr); break; - case ONEWIRE: meter_close_onewire(mtr); break; -#ifdef SML_SUPPORT - case SML: meter_close_sml(mtr); break; -#endif /* SML_SUPPORT */ - default: fprintf(stderr, "error: unknown meter type: %i\n", mtr->type->id); - } -} - -size_t meter_read(meter_t *mtr, reading_t rds[], size_t n) { - switch (mtr->type->id) { - case RANDOM: return meter_read_random(mtr, rds, n); - case S0: return meter_read_s0(mtr, rds, n); - case D0: return meter_read_d0(mtr, rds, n); - case ONEWIRE: return meter_read_onewire(mtr, rds, n); -#ifdef SML_SUPPORT - case SML: return meter_read_sml(mtr, rds, n); -#endif /* SML_SUPPORT */ - default: fprintf(stderr, "error: unknown meter type: %i\n", mtr->type->id); - } - - return 0; -} diff --git a/src/obis.c b/src/obis.c index 26ec8d7..6bcf46c 100644 --- a/src/obis.c +++ b/src/obis.c @@ -29,67 +29,69 @@ #include #include "obis.h" +#include "common.h" #define DC 0xff // wildcard, dont care -obis_alias_t obis_aliases[] = { -/** - * 255 is considered as wildcard! - * - * A B C D E F alias description - * ===================================================================================*/ +static const obis_alias_t aliases[] = { +/* A B C D E F alias description +====================================================================*/ -/* General */ -{{{ 1, 0, 12, 7, DC, DC}}, "voltage", ""}, -{{{ 1, 0, 32, 7, DC, DC}}, "voltage-l1", ""}, -{{{ 1, 0, 52, 7, DC, DC}}, "voltage-l2", ""}, -{{{ 1, 0, 72, 7, DC, DC}}, "voltage-l3", ""}, +/* general */ +{{{ 1, 0, 1, 7, DC, DC}}, "power", "Wirkleistung (Summe)"}, +{{{ 1, 0, 21, 7, DC, DC}}, "power-l1", "Wirkleistung (Phase 1)"}, +{{{ 1, 0, 41, 7, DC, DC}}, "power-l2", "Wirkleistung (Phase 2)"}, +{{{ 1, 0, 61, 7, DC, DC}}, "power-l3", "Wirkleistung (Phase 3)"}, -{{{ 1, 0, 11, 7, DC, DC}}, "current", ""}, -{{{ 1, 0, 31, 7, DC, DC}}, "current-l1", ""}, -{{{ 1, 0, 51, 7, DC, DC}}, "current-l2", ""}, -{{{ 1, 0, 71, 7, DC, DC}}, "current-l3", ""}, +{{{ 1, 0, 12, 7, DC, DC}}, "voltage", "Spannung (Mittelwert)"}, +{{{ 1, 0, 32, 7, DC, DC}}, "voltage-l1", "Spannung (Phase 1)"}, +{{{ 1, 0, 52, 7, DC, DC}}, "voltage-l2", "Spannung (Phase 2)"}, +{{{ 1, 0, 72, 7, DC, DC}}, "voltage-l3", "Spannung (Phase 3)"}, -{{{ 1, 0, 14, 7, 0, DC}}, "frequency", ""}, -{{{ 1, 0, 12, 7, 0, DC}}, "powerfactor", ""}, +{{{ 1, 0, 11, 7, DC, DC}}, "current", "Stromstaerke (Summe)"}, +{{{ 1, 0, 31, 7, DC, DC}}, "current-l1", "Stromstaerke (Phase 1)"}, +{{{ 1, 0, 51, 7, DC, DC}}, "current-l2", "Stromstaerke (Phase 2)"}, +{{{ 1, 0, 71, 7, DC, DC}}, "current-l3", "Stromstaerke (Phase 3)"}, -{{{ 1, 0, 1, 7, DC, DC}}, "power", "Active Power Instantaneous value Total"}, -{{{ 1, 0, 21, 7, DC, DC}}, "power-l1", "L1 Active Power Instantaneous value Total"}, -{{{ 1, 0, 41, 7, DC, DC}}, "power-l2", "L1 Active Power Instantaneous value Total"}, -{{{ 1, 0, 61, 7, DC, DC}}, "power-l3", "L3 Active Power Instantaneous value Total"}, +{{{ 1, 0, 14, 7, 0, DC}}, "frequency", "Netzfrequenz"}, +{{{ 1, 0, 12, 7, 0, DC}}, "powerfactor", "Leistungsfaktor"}, -{{{ 0, 0, 96, 1, DC, DC}}, "device", "Complete device ID"}, -{{{ 1, 0, 96, 5, 5, DC}}, "status", "Meter status flag"}, +{{{ 0, 0, 96, 1, DC, DC}}, "device", "Zaehler Seriennr."}, +{{{ 1, 0, 96, 5, 5, DC}}, "status", "Zaehler Status"}, -{{{ 1, 0, 1, 8, DC, DC}}, "counter", "Active Power Counter Total"}, -{{{ 1, 0, 2, 8, DC, DC}}, "counter-out", "Zählerstand Lieferg."}, +{{{ 1, 0, 1, 8, DC, DC}}, "counter", "Zaehlerstand Wirkleistung"}, +{{{ 1, 0, 2, 8, DC, DC}}, "counter-out", "Zaehlerstand Lieferg."}, /* Easymeter */ /* ESYQ3B (Easymeter Q3B) */ -{{{129, 129, 199, 130, 3, DC}}, "esy-?", ""}, // ??? {{{ 1, 0, 1, 8, 1, DC}}, "esy-counter-t1", "Active Power Counter Tariff 1"}, {{{ 1, 0, 1, 8, 2, DC}}, "esy-counter-t2", "Active Power Counter Tariff 2"}, +//{{{129, 129, 199, 130, 3, DC}}, "", ""}, // ??? /* ESYQ3D (Easymeter Q3D) */ - -{{{ 0, 0, 0, 0, 0, DC}}, "esy-?", ""}, // ??? +//{{{ 0, 0, 0, 0, 0, DC}}, "", ""}, // ??? /* HAG eHZ010C_EHZ1WA02 (Hager eHz) */ {{{ 1, 0, 0, 0, 0, DC}}, "hag-id", "Eigentumsnr."}, -{{{ 1, 0, 96, 50, 0, 0}}, "hag-status", "Netzstatus bitcodiert: Drehfeld, Anlaufschwelle, Energierichtung"}, -{{{ 1, 0, 96, 50, 0, 1}}, "hag-frequency", "Netz-Periode, hexadezimal (Einheit 1/100 ms)"}, -{{{ 1, 0, 96, 50, 0, 2}}, "hag-temp", "aktuelle Chiptemperatur, hexadezimal, Einheit °C"}, +{{{ 1, 0, 96, 50, 0, 0}}, "hag-status", "Netz Status"}, /* bitcodiert: Drehfeld, Anlaufschwelle, Energierichtung */ +{{{ 1, 0, 96, 50, 0, 1}}, "hag-frequency", "Netz Periode"}, /* hexadezimal (Einheit 1/100 ms) */ +{{{ 1, 0, 96, 50, 0, 2}}, "hag-temp", "aktuelle Chiptemperatur"}, /* hexadezimal, Einheit °C */ {{{ 1, 0, 96, 50, 0, 3}}, "hag-temp-min", "minimale Chiptemperatur"}, {{{ 1, 0, 96, 50, 0, 4}}, "hag-temp-avg", "gemittelte Chiptemperatur"}, {{{ 1, 0, 96, 50, 0, 5}}, "hag-temp-max", "maximale Chiptemperatur"}, -{{{ 1, 0, 96, 50, 0, 6}}, "hag-check", "Kontrollnummer"}, +{{{ 1, 0, 96, 50, 0, 6}}, "hag-check", "Kontrollnr."}, {{{ 1, 0, 96, 50, 0, 7}}, "hag-diag", "Diagnose"}, {} /* stop condition for iterator */ }; -obis_id_t * obis_init(obis_id_t *id, const unsigned char *raw) { + +const obis_alias_t * obis_get_aliases() { + return aliases; +} + +obis_id_t * obis_init(obis_id_t *id, unsigned char *raw) { if (raw == NULL) { memset(id->raw, 0, 6); /* initialize with zeros */ } @@ -100,16 +102,15 @@ obis_id_t * obis_init(obis_id_t *id, const unsigned char *raw) { return id; } -int obis_parse(obis_id_t *id, const char *str, size_t n) { +int obis_parse(const char *str, obis_id_t *id) { enum { A = 0, B, C, D, E, F }; - char b; - int num; - int field; + char b = 0; + int num = 0; + int field = -1; + size_t n = strlen(str); - num = b = 0; - field = -1; - memset(&id->raw, 0xff, 6); /* initialize as wildcard */ + memset(&id->raw, DC, 6); /* initialize as wildcard */ /* format: "A-B:C.D.E[*&]F" */ /* fields A, B, E, F are optional */ @@ -133,7 +134,9 @@ int obis_parse(obis_id_t *id, const char *str, size_t n) { else if ((b == '*' || b == '&') && field == D) { /* end of field E, start of field F */ field = E; } - else goto error; // TODO lookup aliases + else { + return ERR; + } id->raw[field] = num; num = 0; @@ -144,25 +147,17 @@ int obis_parse(obis_id_t *id, const char *str, size_t n) { id->raw[++field] = num; /* fields C & D are mandatory */ - if (field < D) goto error; - - return 0; - -error: - printf("something unexpected happened (field=%i, b=%c, num=%i): %s:%i!\n", field, b, num, __FUNCTION__, __LINE__); - return -1; + return (field < D) ? ERR : SUCCESS; } -obis_id_t * obis_lookup_alias(const char *alias) { - obis_alias_t *it = obis_aliases; - - do { /* linear search */ +int obis_lookup_alias(const char *alias, obis_id_t *id) { + for (const obis_alias_t *it = aliases; it != NULL; it++) { if (strcmp(it->name, alias) == 0) { - return &it->id; + *id = it->id; + return SUCCESS; } - } while ((++it)->name); - - return NULL; + } + return ERR_NOT_FOUND; } int obis_unparse(obis_id_t id, char *buffer, size_t n) { diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..778c36d --- /dev/null +++ b/src/options.c @@ -0,0 +1,39 @@ +#include + +#include "options.h" +#include "common.h" + +int options_lookup(list_t options, char *key, void *value, option_type_t type) { + option_t * option = NULL; + + /* linear search */ + foreach(options, val, option_t) { + if (strcmp(val->key, key) == 0) { + option = val; + } + } + + /* checks */ + if (option == NULL) { + return ERR_NOT_FOUND; + } + else if (option->type == type) { + size_t n = 0; + switch (option->type) { + case option_type_boolean: n = sizeof(option->value.integer); break; /* boolean is a bitfield: int boolean:1; */ + case option_type_double: n = sizeof(option->value.floating); break; + case option_type_int: n = sizeof(option->value.integer); break; + case option_type_string: n = sizeof(option->value.string); break; + } + memcpy(value, &option->value, n); + return SUCCESS; + } + else { + return ERR_INVALID_TYPE; + } +} + +int options_lookup_boolean(list_t o, char *k, int *v) { return options_lookup(o, k, v, option_type_boolean); } +int options_lookup_int(list_t o, char *k, int *v) { return options_lookup(o, k, v, option_type_int); } +int options_lookup_string(list_t o, char *k, char **v) { return options_lookup(o, k, v, option_type_string); } +int options_lookup_double(list_t o, char *k, double *v) { return options_lookup(o, k, v, option_type_double); } diff --git a/src/random.c b/src/random.c index 06b7135..1cc9c21 100644 --- a/src/random.c +++ b/src/random.c @@ -29,39 +29,55 @@ #include #include #include +#include #include "meter.h" #include "random.h" +#include "options.h" + +int meter_init_random(meter_t *mtr, list_t options) { + meter_handle_random_t *handle = &mtr->handle.random; + + handle->min = 0; + handle->max = 40; + handle->last = (handle->max + handle->min) / 2; /* start in the middle */ + + if (options_lookup_double(options, "min", &handle->min) == ERR_INVALID_TYPE) { + print(log_error, "Min value has to be a floating point number (e.g. '40.0')", mtr); + return ERR; + } + + if (options_lookup_double(options, "max", &handle->max) == ERR_INVALID_TYPE) { + print(log_error, "Max value has to be a floating point number (e.g. '40.0')", mtr); + return ERR; + } + + return SUCCESS; +} int meter_open_random(meter_t *mtr) { - meter_handle_random_t *handle = &mtr->handle.random; + //meter_handle_random_t *handle = &mtr->handle.random; // TODO rewrite to use /dev/u?random srand(time(NULL)); /* initialize PNRG */ - handle->min = 0; // TODO parse from options - handle->max = strtof(mtr->connection, NULL); - handle->last = handle->max * ((float) rand() / RAND_MAX); /* start value */ - - return 0; /* always succeeds */ + return SUCCESS; /* can't fail */ } -void meter_close_random(meter_t *mtr) { +int meter_close_random(meter_t *mtr) { //meter_handle_random_t *handle = &mtr->handle.random; + + return SUCCESS; } size_t meter_read_random(meter_t *mtr, reading_t rds[], size_t n) { - meter_handle_random_t *handle = &mtr->handle.random; + meter_handle_random_t *handle = &mtr->handle.random; - handle->last += ltqnorm((float) rand() / RAND_MAX); + double step = ltqnorm((float) rand() / RAND_MAX); + double new = handle->last + step; - /* check bounaries */ - if (handle->last > handle->max) { - handle->last = handle->max; - } - else if (handle->last < handle->min) { - handle->last = handle->min; - } + /* check boundaries */ + handle->last += (new > handle->max || new < handle->min) ? -step : step; rds->value = handle->last; gettimeofday(&rds->time, NULL); diff --git a/src/s0.c b/src/s0.c index 084e7c8..6f853ca 100644 --- a/src/s0.c +++ b/src/s0.c @@ -27,10 +27,23 @@ #include #include #include +#include #include #include "meter.h" #include "s0.h" +#include "options.h" + +int meter_init_s0(meter_t *mtr, list_t options) { + meter_handle_s0_t *handle = &mtr->handle.s0; + + if (options_lookup_string(options, "device", &handle->device) != SUCCESS) { + print(log_error, "Missing device or invalid type", mtr); + return ERR; + } + + return SUCCESS; +} /** * Setup serial port @@ -39,11 +52,11 @@ int meter_open_s0(meter_t *mtr) { meter_handle_s0_t *handle = &mtr->handle.s0; /* open port */ - int fd = open(mtr->connection, O_RDWR | O_NOCTTY); + int fd = open(handle->device, O_RDWR | O_NOCTTY); if (fd < 0) { - perror(mtr->connection); - return -1; + print(log_error, "open(%s): %s", mtr, handle->device, strerror(errno)); + return ERR; } /* save current port settings */ @@ -66,17 +79,17 @@ int meter_open_s0(meter_t *mtr) { tcsetattr(fd, TCSANOW, &tio); handle->fd = fd; - return 0; + return SUCCESS; } -void meter_close_s0(meter_t *mtr) { +int meter_close_s0(meter_t *mtr) { meter_handle_s0_t *handle = &mtr->handle.s0; /* reset serial port */ tcsetattr(handle->fd, TCSANOW, &handle->oldtio); /* close serial port */ - close(handle->fd); + return close(handle->fd); } size_t meter_read_s0(meter_t *mtr, reading_t rds[], size_t n) { diff --git a/src/sml.c b/src/sml.c index 391d45a..f39096d 100644 --- a/src/sml.c +++ b/src/sml.c @@ -33,6 +33,7 @@ #include #include #include +#include /* serial port */ #include @@ -50,27 +51,52 @@ #include "meter.h" #include "sml.h" #include "obis.h" +#include "options.h" + +int meter_init_sml(meter_t *mtr, list_t options) { + meter_handle_sml_t *handle = &mtr->handle.sml; + + /* connection */ + handle->host = NULL; + handle->device = NULL; + if (options_lookup_string(options, "host", &handle->host) != SUCCESS && options_lookup_string(options, "device", &handle->device) != SUCCESS) { + print(log_error, "Missing host or port", mtr); + return ERR; + } + + /* baudrate */ + handle->baudrate = 9600; + if (options_lookup_int(options, "baudrate", &handle->baudrate) == ERR_INVALID_TYPE) { + print(log_error, "Invalid type for baudrate", mtr); + return ERR; + } + + return SUCCESS; +} int meter_open_sml(meter_t *mtr) { meter_handle_sml_t *handle = &mtr->handle.sml; - char *addr = strdup(mtr->connection); - char *node = strsep(&addr, ":"); - char *service = strsep(&addr, ":"); + if (handle->device != NULL) { + print(log_error, "TODO: implement serial interface", mtr); + return ERR; + } + else if (handle->host != NULL) { + char *addr = strdup(handle->host); + char *node = strsep(&addr, ":"); + char *service = strsep(&addr, ":"); - handle->fd = meter_sml_open_socket(node, service); - //handle->fd = meter_sml_open_port(args); + handle->fd = meter_sml_open_socket(node, service); + } - free(addr); - - return (handle->fd < 0) ? -1 : 0; + return (handle->fd < 0) ? ERR : SUCCESS; } -void meter_close_sml(meter_t *meter) { +int meter_close_sml(meter_t *meter) { meter_handle_sml_t *handle = &meter->handle.sml; // TODO reset serial port - close(handle->fd); + return close(handle->fd); } size_t meter_read_sml(meter_t *meter, reading_t rds[], size_t n) { @@ -137,8 +163,8 @@ int meter_sml_open_socket(const char *node, const char *service) { fd = socket(PF_INET, SOCK_STREAM, 0); if (fd < 0) { - fprintf(stderr, "error: socket(): %s\n", strerror(errno)); - return -1; + print(log_error, "socket(): %s", NULL, strerror(errno)); + return ERR; } getaddrinfo(node, service, NULL, &ais); @@ -147,8 +173,8 @@ int meter_sml_open_socket(const char *node, const char *service) { res = connect(fd, (struct sockaddr *) &sin, sizeof(sin)); if (res < 0) { - fprintf(stderr, "error: connect(%s, %s): %s\n", node, service, strerror(errno)); - return -1; + print(log_error, "connect(%s, %s): %s", NULL, node, service, strerror(errno)); + return ERR; } return fd; @@ -161,8 +187,8 @@ int meter_sml_open_port(const char *device) { int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY); if (fd < 0) { - fprintf(stderr, "error: open(%s): %s\n", device, strerror(errno)); - return -1; + print(log_error, "open(%s): %s", NULL, device, strerror(errno)); + return ERR; } // set RTS