diff --git a/include/fluksov2.h b/include/fluksov2.h new file mode 100644 index 0000000..24754f3 --- /dev/null +++ b/include/fluksov2.h @@ -0,0 +1,44 @@ +/** + * Replacement for fluksod by directly parsing the fluksometers SPI output + * + * @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 _FLUKSOV2_H_ +#define _FLUKSOV2_H_ + +typedef struct { + char *fifo; + + int fd; /* file descriptor of fifo */ +} meter_handle_fluksov2_t; + +/* forward declarations */ +struct meter; +struct reading; + +int meter_init_fluksov2(struct meter *mtr, list_t options); +int meter_open_fluksov2(struct meter *mtr); +int meter_close_fluksov2(struct meter *mtr); +size_t meter_read_fluksov2(struct meter *mtr, struct reading *rds, size_t n); + +#endif /* _FLUKSOV2_H_ */ diff --git a/include/reading.h b/include/reading.h new file mode 100644 index 0000000..535fae6 --- /dev/null +++ b/include/reading.h @@ -0,0 +1,91 @@ +/** + * Reading related functions + * + * @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 _READING_H_ +#define _READING_H_ + +#include "obis.h" + +#define MAX_IDENTIFIER_LEN 255 + +typedef union reading_id { + obis_id_t obis; + char *string; + char *uuid; + int channel; +} reading_id_t; + +typedef struct reading { + double value; + struct timeval time; + reading_id_t identifier; + + struct reading *next; /* pointer for linked list */ +} reading_t; + +/* prototypes */ + +enum meter_procotol; /* forward declaration */ + +/** + * Parse two reading identifiers in a given protocol context + * + * @return result like in strcmp() + */ +int meter_id_compare(enum meter_procotol, reading_id_t a, reading_id_t b); + +/** + * Parse identifier by a given string and protocol + * + * @param protocol the given protocol context in which the string should be parsed + * @param string the string-encoded identifier + * @return 0 on success, < 0 on error + */ +int reading_id_parse(enum meter_procotol protocol, reading_id_t *id, const char *string); + +/** + * Print identifier to buffer for debugging/dump + * + * @return the amount of bytes used in buffer + */ +size_t reading_id_unparse(enum meter_procotol protocol, reading_id_t identifier, char *buffer, size_t n); + +/** + * Converts timeval structure to double + * + * @param tv the timeval structure + * @return the double value + */ +double tvtod(struct timeval tv); + +/** + * Converts double to timeval structure + * + * @param ts the double value + * @return the timeval strucure + */ +struct timeval dtotv(double ts); + +#endif /* _READING_H_ */ diff --git a/src/fluksov2.c b/src/fluksov2.c new file mode 100644 index 0000000..7cf0d0f --- /dev/null +++ b/src/fluksov2.c @@ -0,0 +1,145 @@ +/** + * Parsing SPI output of the new fluksometer + * + * @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 "fluksov2.h" +#include "options.h" + +int meter_init_fluksov2(meter_t *mtr, list_t options) { + meter_handle_fluksov2_t *handle = &mtr->handle.fluksov2; + + char *fifo; + if (options_lookup_string(options, "fifo", &fifo) == SUCCESS) { + handle->fifo = strdup(fifo); + } + else { + handle->fifo = "/var/run/spid/delta/out"; + } + + return SUCCESS; +} + +int meter_open_fluksov2(meter_t *mtr) { + meter_handle_fluksov2_t *handle = &mtr->handle.fluksov2; + + /* open port */ + handle->fd = open(handle->fifo, O_RDONLY); + + if (handle->fd < 0) { + print(log_error, "open(%s): %s", mtr, handle->fifo, strerror(errno)); + return ERR; + } + + return SUCCESS; +} + +int meter_close_fluksov2(meter_t *mtr) { + meter_handle_fluksov2_t *handle = &mtr->handle.fluksov2; + + return close(handle->fd); /* close fifo */ +} + +size_t read_line(int fd, char *buffer, size_t n) { + int r; /* return code of read() */ + int e = 0; /* end of line? */ + int i = 0; /* iterator for buffer */ + char c; /* character read */ + + while (i < n) { + r = read(fd, &c, 1); /* read byte-per-byte, to identify a line break */ + + if (r < 0) { /* an error occured, pass through to caller */ + return r; + } + else if (r == 1) { /* successful read */ + switch (c) { + case '\n': /* line delimiter */ + case '\r': + end++; + if (end == 2) { + return i; /* line end after "\n\r" */ + } + else { + break; /* wait for second delimiter */ + } + + default: /* normal character */ + buffer[i] = c; + i++; + } + } + } + + return i; +} + +size_t meter_read_fluksov2(meter_t *mtr, reading_t rds[], size_t n) { + meter_handle_fluksov2_t *handle = &mtr->handle.fluksov2; + + char line[64]; + size_t i = 0; /* number of readings */ + size_t h = 0; /* sensor numer */ + + int r = read_line(handle->fd, line, 64); /* blocking read of a complete line */ + if (r <= 0) { + print(log_error, "read(%s): %s", mtr, handle->fifo, strerror(errno)); + return 0; + } + + char *time_str = strtok(line, " \t"); /* first token is the timestamp */ + struct timeval time = { + .tv_sec = strtoq(time_str, NULL, 10) + .tv_usec = 0 /* no millisecond resolution available */ + }; + + for (int j = 0; j < 3 && i < n; j++) { + char *tok = strtok(NULL, " \t"); + if (tok == NULL) { + return i; + } + + switch (j) { + case 0: /* id (integer) */ + break; /* just ignore */ + + case 1: /* consumption (Wh) */ + rds[i].identifier.channel = i; + rds[i].time = time; + rds[i].value = strtod(tok, NULL); + i++; + + case 2: /* power (W) */ + j = 0; + i++; + } + } + + return 0; +} + diff --git a/src/meter.c b/src/meter.c index 626e457..b79542c 100644 --- a/src/meter.c +++ b/src/meter.c @@ -24,9 +24,7 @@ */ #include -#include #include -#include #include "meter.h" #include "options.h" @@ -39,6 +37,7 @@ static const meter_details_t protocols[] = { 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(fluksov2, "Read from onboard SPI of a Flukso v2", 16, FALSE), METER_DETAIL(s0, "S0 on RS232", 1, TRUE), METER_DETAIL(d0, "Plaintext protocol (DIN EN 62056-21)", 32, FALSE), #ifdef SML_SUPPORT @@ -64,16 +63,30 @@ int meter_init(meter_t *mtr, list_t options) { } /* interval */ - mtr->interval = 10; + mtr->interval = -1; /* indicates unknown interval */ 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); + if (details->periodic == TRUE && mtr->interval < 0) { + print(log_error, "Interval has to be positive!", mtr); + } + return details->init_func(mtr, options); } +int meter_lookup_protocol(const char* name, meter_protocol_t *protocol) { + for (const meter_details_t *it = meter_get_protocols(); it != NULL; it++) { + if (strcmp(it->name, name) == 0) { + *protocol = it->id; + return SUCCESS; + } + } + return ERR_NOT_FOUND; +} + int meter_open(meter_t *mtr) { const meter_details_t *details = meter_get_details(mtr->protocol); return details->open_func(mtr); @@ -93,16 +106,6 @@ 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) { @@ -112,19 +115,3 @@ const meter_details_t * meter_get_details(meter_protocol_t protocol) { return NULL; } -double tvtod(struct timeval tv) { - return tv.tv_sec + tv.tv_usec / 1e6; -} - -struct timeval dtotv(double ts) { - double integral; - double fraction = modf(ts, &integral); - - struct timeval tv = { - .tv_usec = (long int) (fraction * 1e6), - .tv_sec = (long int) integral - }; - - return tv; -} - diff --git a/src/reading.c b/src/reading.c new file mode 100644 index 0000000..cb01c46 --- /dev/null +++ b/src/reading.c @@ -0,0 +1,133 @@ +/** + * Reading related functions + * + * @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 "reading.h" +#include "meter.h" + +int reading_id_compare(meter_protocol_t protocol, reading_id_t a, reading_id_t b) { + switch (protocol) { + case meter_protocol_d0: + case meter_protocol_sml: + return obis_compare(a.obis, b.obis); + + case meter_protocol_fluksov2: + return !(a.channel == b.channel); + + case meter_protocol_file: + case meter_protocol_exec: + return strcmp(a.string, b.string); + + default: + /* no channel id, adding all readings to buffer */ + return 0; /* equal */ + } +} + +int reading_id_parse(meter_protocol_t protocol, reading_id_t *id, const char *string) { + switch (protocol) { + case meter_protocol_d0: + case meter_protocol_sml: + if (obis_parse(string, &id->obis) != SUCCESS) { + if (obis_lookup_alias(string, &id->obis) != SUCCESS) { + return ERR; + } + } + break; + + case meter_protocol_fluksov2: { + char type[13]; + int channel; + + int ret = sscanf(string, "sensor%u/%12s", &channel, type); + if (ret != 2) { + return ERR; + } + + id->channel = channel + 1; /* increment by 1 to distinguish between +0 and -0 */ + + if (strcmp(type, "consumption") == 0) { + id->channel *= -1; + } + else if (strcmp(type, "power") != 0) { + return ERR; + } + break; + } + + case meter_protocol_file: + case meter_protocol_exec: + id->string = strdup(string); // TODO free() elsewhere + break; + + default: /* ignore other protocols which do not provide id's */ + break; + } + + return SUCCESS; +} + +size_t reading_id_unparse(meter_protocol_t protocol, reading_id_t id, char *buffer, size_t n) { + switch (protocol) { + case meter_protocol_d0: + case meter_protocol_sml: + obis_unparse(id.obis, buffer, n); + break; + + case meter_protocol_fluksov2: + snprintf(buffer, n, "sensor%u/%s", abs(id.channel) - 1, (id.channel > 0) ? "power" : "consumption"); + break; + + case meter_protocol_file: + case meter_protocol_exec: + if (id.string != NULL) { + strncpy(buffer, id.string, n); + break; + } + + default: + buffer[0] = '\0'; + } + + return strlen(buffer); +} + +double tvtod(struct timeval tv) { + return tv.tv_sec + tv.tv_usec / 1e6; +} + +struct timeval dtotv(double ts) { + double integral; + double fraction = modf(ts, &integral); + + struct timeval tv = { + .tv_usec = (long int) (fraction * 1e6), + .tv_sec = (long int) integral + }; + + return tv; +}