Merge pull request #2 from stv0g/master

new configuration scheme and protocol: "file"
This commit is contained in:
Justin Otherguy 2011-12-05 14:10:34 -08:00
commit f950fe7e37
15 changed files with 360 additions and 155 deletions

View file

@ -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));

View file

@ -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
View file

@ -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\\"

View file

@ -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])

View file

@ -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
}
]}

View file

@ -30,7 +30,7 @@
typedef struct {
char *command;
char *regex;
char *format;
} meter_handle_exec_t;
/* forward declarations */

View file

@ -30,7 +30,8 @@
typedef struct {
char *path;
char *regex;
char *format;
int rewind;
FILE *fd;
} meter_handle_file_t;

View file

@ -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 */

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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", &regex) == 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", &regex) == 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, &timestamp);
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;
}

View file

@ -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;
}

View file

@ -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
View file

@ -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;
}