Merge pull request #2 from stv0g/master
new configuration scheme and protocol: "file"
This commit is contained in:
commit
f950fe7e37
15 changed files with 360 additions and 155 deletions
|
@ -57,24 +57,33 @@ void channel_free(channel_t *ch) {
|
|||
free(ch->middleware);
|
||||
}
|
||||
|
||||
int channel_compare_identifier(meter_protocol_t protocol, reading_id_t a, reading_id_t b) {
|
||||
switch (protocol) {
|
||||
case meter_protocol_d0:
|
||||
case meter_protocol_sml:
|
||||
/* intelligent protocols require matching ids */
|
||||
return obis_compare(a.obis, b.obis);
|
||||
|
||||
case meter_protocol_file:
|
||||
case meter_protocol_exec:
|
||||
return strcmp(a.string, b.string);
|
||||
break;
|
||||
default:
|
||||
/* no channel identifier, adding all readings to buffer */
|
||||
return 0; /* equal */
|
||||
}
|
||||
}
|
||||
|
||||
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 delta;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
int add = FALSE;
|
||||
if (protocol == meter_protocol_d0 || protocol == meter_protocol_sml) { /* intelligent protocols require matching ids */
|
||||
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 (channel_compare_identifier(protocol, rds[i].identifier, ch->identifier) == 0) {
|
||||
|
||||
if (add) {
|
||||
delta = (ch->last == 0) ? 0 : rds[i].value - ch->last; /* ignoring first reading when no preceeding reading is available (no consumption) */
|
||||
/* ignoring first reading when no preceeding reading is available (no consumption) */
|
||||
delta = (ch->last == 0) ? 0 : rds[i].value - ch->last;
|
||||
ch->last = rds[i].value;
|
||||
|
||||
print(log_info, "Adding reading to queue (value=%.2f delta=%.2f ts=%.3f)", ch, rds[i].value, delta, tvtod(rds[i].time));
|
||||
|
|
|
@ -64,10 +64,26 @@ void * reading_thread(void *arg) {
|
|||
/* 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];
|
||||
char identifier[255]; // TODO choose correct length
|
||||
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));
|
||||
switch (mtr->protocol) {
|
||||
case meter_protocol_d0:
|
||||
case meter_protocol_sml:
|
||||
obis_unparse(rds[i].identifier.obis, identifier, 255);
|
||||
break;
|
||||
|
||||
case meter_protocol_file:
|
||||
case meter_protocol_exec:
|
||||
if (rds[i].identifier.string != NULL) {
|
||||
strncpy(identifier, rds[i].identifier.string, 255);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
identifier[0] = '\0';
|
||||
}
|
||||
|
||||
print(log_debug, "Reading: id=%s value=%.2f ts=%.3f", mtr, identifier, rds[i].value, tvtod(rds[i].time));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
20
configure
vendored
20
configure
vendored
|
@ -1,6 +1,6 @@
|
|||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.67 for vzlogger 0.3.1.
|
||||
# Generated by GNU Autoconf 2.67 for vzlogger 0.3.2.
|
||||
#
|
||||
# Report bugs to <http://bugs.volkszaehler.org>.
|
||||
#
|
||||
|
@ -552,8 +552,8 @@ MAKEFLAGS=
|
|||
# Identity of this package.
|
||||
PACKAGE_NAME='vzlogger'
|
||||
PACKAGE_TARNAME='vzlogger'
|
||||
PACKAGE_VERSION='0.3.1'
|
||||
PACKAGE_STRING='vzlogger 0.3.1'
|
||||
PACKAGE_VERSION='0.3.2'
|
||||
PACKAGE_STRING='vzlogger 0.3.2'
|
||||
PACKAGE_BUGREPORT='http://bugs.volkszaehler.org'
|
||||
PACKAGE_URL=''
|
||||
|
||||
|
@ -1263,7 +1263,7 @@ if test "$ac_init_help" = "long"; then
|
|||
# Omit some internal or obsolete options to make the list less imposing.
|
||||
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||
cat <<_ACEOF
|
||||
\`configure' configures vzlogger 0.3.1 to adapt to many kinds of systems.
|
||||
\`configure' configures vzlogger 0.3.2 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
|
@ -1329,7 +1329,7 @@ fi
|
|||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of vzlogger 0.3.1:";;
|
||||
short | recursive ) echo "Configuration of vzlogger 0.3.2:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
|
@ -1442,7 +1442,7 @@ fi
|
|||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
vzlogger configure 0.3.1
|
||||
vzlogger configure 0.3.2
|
||||
generated by GNU Autoconf 2.67
|
||||
|
||||
Copyright (C) 2010 Free Software Foundation, Inc.
|
||||
|
@ -1911,7 +1911,7 @@ cat >config.log <<_ACEOF
|
|||
This file contains any messages produced by compilers while
|
||||
running configure, to aid debugging if configure makes a mistake.
|
||||
|
||||
It was created by vzlogger $as_me 0.3.1, which was
|
||||
It was created by vzlogger $as_me 0.3.2, which was
|
||||
generated by GNU Autoconf 2.67. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
|
@ -2726,7 +2726,7 @@ fi
|
|||
|
||||
# Define the identity of the package.
|
||||
PACKAGE=vzlogger
|
||||
VERSION=0.3.1
|
||||
VERSION=0.3.2
|
||||
|
||||
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
|
@ -5661,7 +5661,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
|||
# report actual input values of CONFIG_FILES etc. instead of their
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by vzlogger $as_me 0.3.1, which was
|
||||
This file was extended by vzlogger $as_me 0.3.2, which was
|
||||
generated by GNU Autoconf 2.67. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
|
@ -5727,7 +5727,7 @@ _ACEOF
|
|||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
vzlogger config.status 0.3.1
|
||||
vzlogger config.status 0.3.2
|
||||
configured by $0, generated by GNU Autoconf 2.67,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
AC_PREREQ([2.67])
|
||||
|
||||
# update version here!
|
||||
AC_INIT([vzlogger], [0.3.1], [http://bugs.volkszaehler.org])
|
||||
AM_INIT_AUTOMAKE([vzlogger], [0.3.1])
|
||||
AC_INIT([vzlogger], [0.3.2], [http://bugs.volkszaehler.org])
|
||||
AM_INIT_AUTOMAKE([vzlogger], [0.3.2])
|
||||
|
||||
AC_CONFIG_SRCDIR([src/meter.c])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
"meters" : [{
|
||||
"enabled" : false, /* disabled meters will be ignored */
|
||||
"protocol" : "sml", /* use 'vzlogger -h' for list of available protocols */
|
||||
"connection" : "meinzaehler.dyndns.info:7331",
|
||||
"host" : "meinzaehler.dyndns.info:7331",
|
||||
"channels": [{
|
||||
"uuid" : "fde8f1d0-c5d0-11e0-856e-f9e4360ced10",
|
||||
"middleware" : "http://localhost/workspace/volkszaehler.org/volkszaehler.org/htdocs/middleware.php",
|
||||
|
@ -34,7 +34,8 @@
|
|||
}, {
|
||||
"uuid" : "a8da012a-9eb4-49ed-b7f3-38c95142a90c",
|
||||
"middleware" : "http://localhost/workspace/volkszaehler.org/volkszaehler.org/htdocs/middleware.php",
|
||||
"identifier" : "voltage"
|
||||
"identifier" : "counter",
|
||||
"counter" : true /* send only delta between to values (simluate impulses) */
|
||||
}, {
|
||||
"uuid" : "d5c6db0f-533e-498d-a85a-be972c104b48",
|
||||
"middleware" : "http://localhost/workspace/volkszaehler.org/volkszaehler.org/htdocs/middleware.php",
|
||||
|
@ -43,17 +44,25 @@
|
|||
}, {
|
||||
"protocol" : "random",
|
||||
"interval" : 2,
|
||||
"connection" : "40",
|
||||
"max" : 40.0, /* has to be double! */
|
||||
"min" : -5.0,
|
||||
"channel" : {
|
||||
"uuid" : "bac2e840-f72c-11e0-bedf-3f850c1e5a66",
|
||||
"middleware" : "http://localhost/workspace/volkszaehler.org/volkszaehler.org/htdocs/middleware.php"
|
||||
}
|
||||
}, {
|
||||
"protocol" : "s0",
|
||||
"connection" : "/dev/ttyUSB0",
|
||||
"port" : "/dev/ttyUSB0",
|
||||
"channel" : {
|
||||
"uuid" : "d495a390-f747-11e0-b3ca-f7890e45c7b2",
|
||||
"middleware" : "http://localhost/workspace/volkszaehler.org/volkszaehler.org/htdocs/middleware.php"
|
||||
}
|
||||
},
|
||||
{
|
||||
"protocol" : "file",
|
||||
"path" : "/proc/loadavg",
|
||||
// "format" : "$i $v $t", /* $i => identifier, $v => value, $t => timestamp, arbitrary text and whitespaces are allowed */
|
||||
"rewind" : true, /* reset file pointer each interval to the beginning of the file */
|
||||
"interval" : 2
|
||||
}
|
||||
]}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
typedef struct {
|
||||
char *command;
|
||||
char *regex;
|
||||
char *format;
|
||||
} meter_handle_exec_t;
|
||||
|
||||
/* forward declarations */
|
||||
|
|
|
@ -30,7 +30,8 @@
|
|||
|
||||
typedef struct {
|
||||
char *path;
|
||||
char *regex;
|
||||
char *format;
|
||||
int rewind;
|
||||
|
||||
FILE *fd;
|
||||
} meter_handle_file_t;
|
||||
|
|
|
@ -27,13 +27,13 @@
|
|||
#define _S0_H_
|
||||
|
||||
#include <termios.h>
|
||||
#include <signal.h>
|
||||
|
||||
typedef struct {
|
||||
char *device;
|
||||
int resolution;
|
||||
|
||||
int fd; /* file descriptor of port */
|
||||
struct termios oldtio; /* required to reset port */
|
||||
int fd; /* file descriptor of port */
|
||||
struct termios old_tio; /* required to reset port */
|
||||
} meter_handle_s0_t;
|
||||
|
||||
/* forward declarations */
|
||||
|
|
|
@ -35,29 +35,23 @@
|
|||
#include <sml/sml_file.h>
|
||||
#include <sml/sml_value.h>
|
||||
|
||||
#include <termios.h>
|
||||
|
||||
#include "obis.h"
|
||||
|
||||
typedef struct {
|
||||
char *host;
|
||||
char *device;
|
||||
int baudrate;
|
||||
speed_t baudrate;
|
||||
|
||||
int fd;
|
||||
//termios old_tio;
|
||||
int fd; /* file descriptor of port */
|
||||
struct termios old_tio; /* required to reset port */
|
||||
} meter_handle_sml_t;
|
||||
|
||||
/* forward declarations */
|
||||
struct meter;
|
||||
struct reading;
|
||||
|
||||
/**
|
||||
* Cast arbitrary sized sml_value to double
|
||||
*
|
||||
* @param value the sml_value which should be casted
|
||||
* @return double value representation of sml_value, NAN if an error occured
|
||||
*/
|
||||
double sml_value_todouble(sml_value *value);
|
||||
|
||||
/**
|
||||
* Initialize meter structure with a list of options
|
||||
*
|
||||
|
@ -107,9 +101,11 @@ void meter_sml_parse(sml_list *list, struct reading *rd);
|
|||
* Open serial port by device
|
||||
*
|
||||
* @param device the device path, usually /dev/ttyS*
|
||||
* @param old_config pointer to termios structure, will be filled with old port configuration
|
||||
* @param baudrate the baudrate
|
||||
* @return file descriptor, <0 on error
|
||||
*/
|
||||
int meter_sml_open_port(const char *device);
|
||||
int meter_sml_open_device(const char *device, struct termios *old_config, speed_t baudrate);
|
||||
|
||||
/**
|
||||
* Open socket
|
||||
|
|
87
src/d0.c
87
src/d0.c
|
@ -11,7 +11,6 @@
|
|||
* @license http://www.gnu.org/licenses/gpl.txt GNU Public License
|
||||
* @author Steffen Vogel <info@steffenvogel.de>
|
||||
* @author Mathias Dalheimer <md@gonium.net>
|
||||
* based heavily on libehz (https://github.com/gonium/libehz.git)
|
||||
*/
|
||||
/*
|
||||
* This file is part of volkzaehler.org
|
||||
|
@ -34,11 +33,11 @@
|
|||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
/* socket */
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
|
@ -47,47 +46,33 @@
|
|||
#include "obis.h"
|
||||
#include "options.h"
|
||||
|
||||
int meter_d0_open_socket(const char *node, const char *service) {
|
||||
struct sockaddr_in sin;
|
||||
struct addrinfo *ais;
|
||||
int fd; // file descriptor
|
||||
int res;
|
||||
|
||||
fd = socket(PF_INET, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
print(log_error, "socket(): %s", NULL, strerror(errno));
|
||||
return ERR;
|
||||
}
|
||||
|
||||
getaddrinfo(node, service, NULL, &ais);
|
||||
memcpy(&sin, ais->ai_addr, ais->ai_addrlen);
|
||||
freeaddrinfo(ais);
|
||||
|
||||
res = connect(fd, (struct sockaddr *) &sin, sizeof(sin));
|
||||
if (res < 0) {
|
||||
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);
|
||||
char *host, *device;
|
||||
if (options_lookup_string(options, "host", &host) == SUCCESS) {
|
||||
handle->host = strdup(host);
|
||||
handle->device = NULL;
|
||||
}
|
||||
else if (options_lookup_string(options, "device", &device) == SUCCESS) {
|
||||
handle->device = strdup(device);
|
||||
handle->host = NULL;
|
||||
}
|
||||
else {
|
||||
print(log_error, "Missing host and 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;
|
||||
switch (options_lookup_int(options, "baudrate", &handle->baudrate)) {
|
||||
case ERR_NOT_FOUND: /* using default value if not specified */
|
||||
handle->baudrate = 9600;
|
||||
break;
|
||||
|
||||
default:
|
||||
print(log_error, "Failed to parse the baudrate", mtr);
|
||||
return ERR;
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
|
@ -236,8 +221,9 @@ size_t meter_read_d0(meter_t *mtr, reading_t rds[], size_t max_readings) {
|
|||
|
||||
case END_LINE:
|
||||
if (byte == '\r' || byte == '\n') {
|
||||
if ((number_of_tuples < max_readings) && (strlen(obis_code) > 0) && (strlen(value) > 0)) { /* free slots available and sain content? */
|
||||
printf("parsed reading (OBIS code=%s, value=%s, unit=%s)\n", obis_code, value, unit);
|
||||
/* free slots available and sain content? */
|
||||
if ((number_of_tuples < max_readings) && (strlen(obis_code) > 0) && (strlen(value) > 0)) {
|
||||
print(log_debug, "Parsed reading (OBIS code=%s, value=%s, unit=%s)", mtr, obis_code, value, unit);
|
||||
rds[number_of_tuples].value = strtof(value, NULL);
|
||||
obis_parse(obis_code, &rds[number_of_tuples].identifier.obis);
|
||||
gettimeofday(&rds[number_of_tuples].time, NULL);
|
||||
|
@ -251,7 +237,8 @@ size_t meter_read_d0(meter_t *mtr, reading_t rds[], size_t max_readings) {
|
|||
break;
|
||||
|
||||
case END:
|
||||
print(log_debug, "Read package with %i tuples (vendor=%s, baudrate=%c, identification=%s)", mtr, number_of_tuples, vendor, baudrate, identification);
|
||||
print(log_debug, "Read package with %i tuples (vendor=%s, baudrate=%c, identification=%s)",
|
||||
mtr, number_of_tuples, vendor, baudrate, identification);
|
||||
return number_of_tuples;
|
||||
}
|
||||
}
|
||||
|
@ -261,3 +248,27 @@ error:
|
|||
return 0;
|
||||
}
|
||||
|
||||
int meter_d0_open_socket(const char *node, const char *service) {
|
||||
struct sockaddr_in sin;
|
||||
struct addrinfo *ais;
|
||||
int fd; /* file descriptor */
|
||||
int res;
|
||||
|
||||
fd = socket(PF_INET, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
print(log_error, "socket(): %s", NULL, strerror(errno));
|
||||
return ERR;
|
||||
}
|
||||
|
||||
getaddrinfo(node, service, NULL, &ais);
|
||||
memcpy(&sin, ais->ai_addr, ais->ai_addrlen);
|
||||
freeaddrinfo(ais);
|
||||
|
||||
res = connect(fd, (struct sockaddr *) &sin, sizeof(sin));
|
||||
if (res < 0) {
|
||||
print(log_error, "connect(%s, %s): %s", NULL, node, service, strerror(errno));
|
||||
return ERR;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
|
27
src/exec.c
27
src/exec.c
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Get data by calling programs
|
||||
* Get data by calling a program
|
||||
*
|
||||
* @package vzlogger
|
||||
* @copyright Copyright (c) 2011, The volkszaehler.org project
|
||||
|
@ -33,17 +33,32 @@
|
|||
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) {
|
||||
char *command;
|
||||
if (options_lookup_string(options, "command", &command) == SUCCESS) {
|
||||
handle->command = strdup(command);
|
||||
}
|
||||
else {
|
||||
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;
|
||||
char *format;
|
||||
switch (options_lookup_string(options, "format", &format)) {
|
||||
case SUCCESS:
|
||||
handle->format = strdup(format);
|
||||
// TODO parse format (see file.c)
|
||||
break;
|
||||
|
||||
case ERR_NOT_FOUND:
|
||||
handle->format = NULL; /* use default format */
|
||||
break;
|
||||
|
||||
default:
|
||||
print(log_error, "Failed to parse format", mtr);
|
||||
return ERR;
|
||||
}
|
||||
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
|
|
128
src/file.c
128
src/file.c
|
@ -34,6 +34,7 @@
|
|||
int meter_init_file(meter_t *mtr, list_t options) {
|
||||
meter_handle_file_t *handle = &mtr->handle.file;
|
||||
|
||||
/* path to file which should be read */
|
||||
char *path;
|
||||
if (options_lookup_string(options, "path", &path) == SUCCESS) {
|
||||
handle->path = strdup(path);
|
||||
|
@ -43,13 +44,83 @@ int meter_init_file(meter_t *mtr, list_t options) {
|
|||
return ERR;
|
||||
}
|
||||
|
||||
char *regex;
|
||||
if (options_lookup_string(options, "regex", ®ex) == SUCCESS) {
|
||||
handle->regex = strdup(regex);
|
||||
/* a optional format string for scanf() */
|
||||
char *config_format;
|
||||
switch (options_lookup_string(options, "format", &config_format)) {
|
||||
case SUCCESS: {
|
||||
/**
|
||||
* Compiling the provided format string in a format string for scanf
|
||||
* by replacing the following tokens
|
||||
*
|
||||
* "$v" => "%1$f" (value)
|
||||
* "$i" => "%2$ms" (identifier) (memory gets allocated by sscanf())
|
||||
* "$t" => "%3$f" (timestamp)
|
||||
*/
|
||||
|
||||
int config_len = strlen(config_format);
|
||||
int scanf_len = config_len + 8; /* adding extra space for longer conversion specification in scanf_format */
|
||||
|
||||
char *scanf_format = malloc(scanf_len); /* the scanf format string */
|
||||
|
||||
int i = 0; /* index in config_format string */
|
||||
int j = 0; /* index in scanf_format string */
|
||||
while (i <= config_len && j <= scanf_len) {
|
||||
switch (config_format[i]) {
|
||||
case '$':
|
||||
if (i+1 < config_len) { /* introducing a token */
|
||||
switch (config_format[i+1]) {
|
||||
case 'v': j += sprintf(scanf_format+j, "%%1$f"); break;
|
||||
case 'i': j += sprintf(scanf_format+j, "%%2$ms"); break;
|
||||
case 't': j += sprintf(scanf_format+j, "%%3$lf"); break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
|
||||
case '%':
|
||||
scanf_format[j++] = '%'; /* add double %% to escape a conversion identifier */
|
||||
|
||||
default:
|
||||
scanf_format[j++] = config_format[i]; /* just copying */
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
print(log_debug, "Parsed format string \"%s\" => \"%s\"", mtr, config_format, scanf_format);
|
||||
handle->format = scanf_format;
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case ERR_NOT_FOUND:
|
||||
handle->format = NULL; /* disable format matching */
|
||||
break;
|
||||
|
||||
default:
|
||||
print(log_error, "Failed to parse 'format'", mtr);
|
||||
return ERR;
|
||||
}
|
||||
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;
|
||||
|
||||
/* should we start each time at the beginning of the file? */
|
||||
/* or do we read from a logfile (append) */
|
||||
int rewind;
|
||||
switch (options_lookup_boolean(options, "rewind", &rewind)) {
|
||||
case SUCCESS:
|
||||
handle->rewind = rewind;
|
||||
break;
|
||||
|
||||
case ERR_INVALID_TYPE:
|
||||
print(log_error, "Invalid type for 'rewind'", mtr);
|
||||
return ERR;
|
||||
|
||||
case ERR_NOT_FOUND:
|
||||
handle->rewind = FALSE; /* do not rewind file by default */
|
||||
break;
|
||||
|
||||
default:
|
||||
print(log_error, "Failed to parse 'rewind'", mtr);
|
||||
return ERR;
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
|
@ -77,19 +148,42 @@ int meter_close_file(meter_t *mtr) {
|
|||
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;
|
||||
// TODO use inotify to block eading until file changes
|
||||
|
||||
rewind(handle->fd);
|
||||
bytes = fread(buffer, 1, 16, handle->fd);
|
||||
buffer[bytes] = '\0'; /* zero terminated, required? */
|
||||
char line[256], *endptr;
|
||||
|
||||
if (bytes) {
|
||||
rds->value = strtof(buffer, NULL);
|
||||
gettimeofday(&rds->time, NULL);
|
||||
|
||||
return 1;
|
||||
/* reset file pointer to beginning of file */
|
||||
if (handle->rewind) {
|
||||
rewind(handle->fd);
|
||||
}
|
||||
|
||||
return 0; /* empty file */
|
||||
int i = 0;
|
||||
while (fgets(line, 256, handle->fd) && i < n) {
|
||||
char *nl;
|
||||
if ((nl = strrchr(line, '\n'))) *nl = '\0'; /* remove trailing newline */
|
||||
if ((nl = strrchr(line, '\r'))) *nl = '\0';
|
||||
|
||||
if (handle->format) {
|
||||
double timestamp;
|
||||
|
||||
/* at least the value has to been read */
|
||||
int found = sscanf(line, handle->format, &rds[i].value, &rds[i].identifier.string, ×tamp);
|
||||
if (found >= 1) { // TODO free() space allocated for identifier string
|
||||
rds[i].time = dtotv(timestamp); /* convert double to timeval */
|
||||
i++; /* read successfully */
|
||||
}
|
||||
}
|
||||
else { /* just reading a value per line */
|
||||
rds[i].value = strtod(line, &endptr);
|
||||
rds[i].identifier.string = NULL;
|
||||
|
||||
gettimeofday(&rds[i].time, NULL);
|
||||
|
||||
if (endptr != line) {
|
||||
i++; /* read successfully */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
|
13
src/meter.c
13
src/meter.c
|
@ -36,8 +36,8 @@
|
|||
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(file, "Read from file (ex. 1-Wire sensors via OWFS)", 32, TRUE),
|
||||
//METER_DETAIL(exec, "Read from program (ex. 1-Wire sensors via digitemp)", 32, 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),
|
||||
|
@ -117,8 +117,13 @@ double tvtod(struct timeval tv) {
|
|||
}
|
||||
|
||||
struct timeval dtotv(double ts) {
|
||||
struct timeval tv;
|
||||
tv.tv_usec = modf(ts, &tv.tv_sec);
|
||||
double integral;
|
||||
double fraction = modf(ts, &integral);
|
||||
|
||||
struct timeval tv = {
|
||||
.tv_usec = (long int) (fraction * 1e6),
|
||||
.tv_sec = (long int) integral
|
||||
};
|
||||
|
||||
return tv;
|
||||
}
|
||||
|
|
27
src/s0.c
27
src/s0.c
|
@ -37,17 +37,28 @@
|
|||
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) {
|
||||
char *device;
|
||||
if (options_lookup_string(options, "device", &device) == SUCCESS) {
|
||||
handle->device = strdup(device);
|
||||
}
|
||||
else {
|
||||
print(log_error, "Missing device or invalid type", mtr);
|
||||
return ERR;
|
||||
}
|
||||
|
||||
switch (options_lookup_int(options, "resolution", &handle->resolution)) {
|
||||
case ERR_NOT_FOUND:
|
||||
handle->resolution = 1; /* 1 Wh per impulse */
|
||||
break;
|
||||
|
||||
case ERR_INVALID_TYPE:
|
||||
print(log_error, "Failed to parse resolution", mtr);
|
||||
return ERR;
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup serial port
|
||||
*/
|
||||
int meter_open_s0(meter_t *mtr) {
|
||||
meter_handle_s0_t *handle = &mtr->handle.s0;
|
||||
|
||||
|
@ -60,7 +71,7 @@ int meter_open_s0(meter_t *mtr) {
|
|||
}
|
||||
|
||||
/* save current port settings */
|
||||
tcgetattr(fd, &handle->oldtio);
|
||||
tcgetattr(fd, &handle->old_tio);
|
||||
|
||||
/* configure port */
|
||||
struct termios tio;
|
||||
|
@ -85,11 +96,9 @@ int meter_open_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);
|
||||
tcsetattr(handle->fd, TCSANOW, &handle->old_tio); /* reset serial port */
|
||||
|
||||
/* close serial port */
|
||||
return close(handle->fd);
|
||||
return close(handle->fd); /* close serial port */
|
||||
}
|
||||
|
||||
size_t meter_read_s0(meter_t *mtr, reading_t rds[], size_t n) {
|
||||
|
|
108
src/sml.c
108
src/sml.c
|
@ -38,7 +38,6 @@
|
|||
/* serial port */
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
|
||||
/* socket */
|
||||
#include <netdb.h>
|
||||
|
@ -57,18 +56,49 @@ 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);
|
||||
char *host, *device;
|
||||
if (options_lookup_string(options, "host", &host) == SUCCESS) {
|
||||
handle->host = strdup(host);
|
||||
handle->device = NULL;
|
||||
}
|
||||
else if (options_lookup_string(options, "device", &device) == SUCCESS) {
|
||||
handle->device = strdup(device);
|
||||
handle->host = NULL;
|
||||
}
|
||||
else {
|
||||
print(log_error, "Missing host and 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;
|
||||
int baudrate;
|
||||
switch (options_lookup_int(options, "baudrate", &baudrate)) {
|
||||
case SUCCESS:
|
||||
/* find constant for termios structure */
|
||||
switch (baudrate) {
|
||||
case 1200: handle->baudrate = B1200; break;
|
||||
case 1800: handle->baudrate = B1800; break;
|
||||
case 2400: handle->baudrate = B2400; break;
|
||||
case 4800: handle->baudrate = B4800; break;
|
||||
case 9600: handle->baudrate = B9600; break;
|
||||
case 19200: handle->baudrate = B19200; break;
|
||||
case 38400: handle->baudrate = B38400; break;
|
||||
case 57600: handle->baudrate = B57600; break;
|
||||
case 115200: handle->baudrate = B115200; break;
|
||||
case 230400: handle->baudrate = B230400; break;
|
||||
default:
|
||||
print(log_error, "Invalid baudrate: %i", mtr, baudrate);
|
||||
return ERR;
|
||||
}
|
||||
break;
|
||||
|
||||
case ERR_NOT_FOUND: /* using default value if not specified */
|
||||
handle->baudrate = B9600;
|
||||
break;
|
||||
|
||||
default:
|
||||
print(log_error, "Failed to parse the baudrate", mtr);
|
||||
return ERR;
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
|
@ -77,16 +107,17 @@ int meter_init_sml(meter_t *mtr, list_t options) {
|
|||
int meter_open_sml(meter_t *mtr) {
|
||||
meter_handle_sml_t *handle = &mtr->handle.sml;
|
||||
|
||||
if (handle->device != NULL) {
|
||||
print(log_error, "TODO: implement serial interface", mtr);
|
||||
return ERR;
|
||||
if (handle->device != NULL) { /* local connection */
|
||||
handle->fd = meter_sml_open_device(handle->device, &handle->old_tio, handle->baudrate);
|
||||
}
|
||||
else if (handle->host != NULL) {
|
||||
else if (handle->host != NULL) { /* remote connection */
|
||||
char *addr = strdup(handle->host);
|
||||
char *node = strsep(&addr, ":");
|
||||
char *node = strsep(&addr, ":"); /* split port/service from hostname */
|
||||
char *service = strsep(&addr, ":");
|
||||
|
||||
handle->fd = meter_sml_open_socket(node, service);
|
||||
|
||||
free(addr);
|
||||
}
|
||||
|
||||
return (handle->fd < 0) ? ERR : SUCCESS;
|
||||
|
@ -95,7 +126,11 @@ int meter_open_sml(meter_t *mtr) {
|
|||
int meter_close_sml(meter_t *meter) {
|
||||
meter_handle_sml_t *handle = &meter->handle.sml;
|
||||
|
||||
// TODO reset serial port
|
||||
if (handle->device != NULL) {
|
||||
/* reset serial port */
|
||||
tcsetattr(handle->fd, TCSANOW, &handle->old_tio);
|
||||
}
|
||||
|
||||
return close(handle->fd);
|
||||
}
|
||||
|
||||
|
@ -109,7 +144,7 @@ size_t meter_read_sml(meter_t *meter, reading_t rds[], size_t n) {
|
|||
sml_get_list_response *body;
|
||||
sml_list *entry;
|
||||
|
||||
/* blocking read from fd */
|
||||
/* wait until a we receive a new datagram from the meter (blocking read) */
|
||||
bytes = sml_transport_read(handle->fd, buffer, SML_BUFFER_LEN);
|
||||
|
||||
/* parse SML file & stripping escape sequences */
|
||||
|
@ -145,14 +180,13 @@ void meter_sml_parse(sml_list *entry, reading_t *rd) {
|
|||
|
||||
obis_init(&rd->identifier.obis, entry->obj_name->str);
|
||||
|
||||
/* get time */
|
||||
// TODO handle SML_TIME_SEC_INDEX or time by SML File/Message
|
||||
if (entry->val_time) {
|
||||
if (entry->val_time) { /* use time from meter */
|
||||
rd->time.tv_sec = *entry->val_time->data.timestamp;
|
||||
rd->time.tv_usec = 0;
|
||||
}
|
||||
else {
|
||||
gettimeofday(&rd->time, NULL);
|
||||
gettimeofday(&rd->time, NULL); /* use local time */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,10 +214,10 @@ int meter_sml_open_socket(const char *node, const char *service) {
|
|||
return fd;
|
||||
}
|
||||
|
||||
int meter_sml_open_port(const char *device) {
|
||||
int meter_sml_open_device(const char *device, struct termios *old_tio, speed_t baudrate) {
|
||||
int bits;
|
||||
struct termios config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
struct termios tio;
|
||||
memset(&tio, 0, sizeof(struct termios));
|
||||
|
||||
int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
|
||||
if (fd < 0) {
|
||||
|
@ -191,24 +225,30 @@ int meter_sml_open_port(const char *device) {
|
|||
return ERR;
|
||||
}
|
||||
|
||||
// set RTS
|
||||
/* enable RTS as supply for infrared adapters */
|
||||
ioctl(fd, TIOCMGET, &bits);
|
||||
bits |= TIOCM_RTS;
|
||||
ioctl(fd, TIOCMSET, &bits);
|
||||
|
||||
tcgetattr(fd, &config) ;
|
||||
/* get old configuration */
|
||||
tcgetattr(fd, &tio) ;
|
||||
|
||||
// set 8-N-1
|
||||
config.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
||||
config.c_oflag &= ~OPOST;
|
||||
config.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||
config.c_cflag &= ~(CSIZE | PARENB | PARODD | CSTOPB);
|
||||
config.c_cflag |= CS8;
|
||||
/* backup old configuration to restore it when closing the meter connection */
|
||||
memcpy(old_tio, &tio, sizeof(struct termios));
|
||||
|
||||
// set speed to 9600 baud
|
||||
cfsetispeed( &config, B9600);
|
||||
cfsetospeed( &config, B9600);
|
||||
/* set 8-N-1 */
|
||||
tio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
||||
tio.c_oflag &= ~OPOST;
|
||||
tio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||
tio.c_cflag &= ~(CSIZE | PARENB | PARODD | CSTOPB);
|
||||
tio.c_cflag |= CS8;
|
||||
|
||||
/* set baudrate */
|
||||
cfsetispeed(&tio, baudrate);
|
||||
cfsetospeed(&tio, baudrate);
|
||||
|
||||
/* apply new configuration */
|
||||
tcsetattr(fd, TCSANOW, &tio);
|
||||
|
||||
tcsetattr(fd, TCSANOW, &config);
|
||||
return fd;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue