vzlogger/src/protocols/d0.c

295 lines
8.4 KiB
C

/**
* Plaintext protocol according to DIN EN 62056-21
*
* This protocol uses OBIS codes to identify the readout data
*
* This is our example protocol. Use this skeleton to add your own
* protocols and meters.
*
* @package vzlogger
* @copyright Copyright (c) 2011, The volkszaehler.org project
* @license http://www.gnu.org/licenses/gpl.txt GNU Public License
* @author Steffen Vogel <info@steffenvogel.de>
* @author Mathias Dalheimer <md@gonium.net>
*/
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <sys/time.h>
/* socket */
#include <netdb.h>
#include <sys/socket.h>
#include "meter.h"
#include "protocols/d0.h"
#include "obis.h"
#include "options.h"
int meter_init_d0(meter_t *mtr, list_t options) {
meter_handle_d0_t *handle = &mtr->handle.d0;
/* connection */
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 device or host", mtr);
return ERR;
}
/* baudrate */
int baudrate = 9600; /* default to avoid compiler warning */
switch (options_lookup_int(options, "baudrate", &handle->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 = 9600;
break;
default:
print(log_error, "Failed to parse the baudrate", mtr);
return ERR;
}
return SUCCESS;
}
int meter_open_d0(meter_t *mtr) {
meter_handle_d0_t *handle = &mtr->handle.d0;
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_d0_open_socket(node, service);
}
return (handle->fd < 0) ? ERR : SUCCESS;
}
int meter_close_d0(meter_t *mtr) {
meter_handle_d0_t *handle = &mtr->handle.d0;
return close(handle->fd);
}
size_t meter_read_d0(meter_t *mtr, reading_t rds[], size_t max_readings) {
meter_handle_d0_t *handle = &mtr->handle.d0;
enum { START, VENDOR, BAUDRATE, IDENTIFICATION, START_LINE, OBIS_CODE, VALUE, UNIT, END_LINE, END } context;
char vendor[3+1]; /* 3 upper case vendor + '\0' termination */
char identification[16+1]; /* 16 meter specific + '\0' termination */
char obis_code[16+1]; /* A-B:C.D.E*F
fields A, B, E, F are optional
fields C & D are mandatory
A: energy type; 1: energy
B: channel number; 0: no channel specified
C: data items; 0-89 in COSEM context: IEC 62056-62, Clause D.1; 96: General service entries
1: Totel Active power+
21: L1 Active power+
31: L1 Current
32: L1 Voltage
41: L2 Active power+
51: L2 Current
52: L2 Voltage
61: L3 Active power+
71: L3 Current
72: L3 Voltage
96.1.255: Metering point ID 256 (electricity related)
96.5.5: Meter started status flag
D: types
E: further processing or classification of quantities
F: storage of data
see DIN-EN-62056-61 */
char value[32+1]; /* value, i.e. the actual reading */
char unit[16+1]; /* the unit of the value, e.g. kWh, V, ... */
char baudrate; /* 1 byte for */
char byte; /* we parse our input byte wise */
int byte_iterator;
int number_of_tuples;
byte_iterator = number_of_tuples = baudrate = 0;
context = START; /* start with context START */
while (read(handle->fd, &byte, 1)) {
if (byte == '/') context = START; /* reset to START if "/" reoccurs */
else if (byte == '!') context = END; /* "!" is the identifier for the END */
switch (context) {
case START: /* strip the initial "/" */
byte_iterator = number_of_tuples = 0; /* start */
context = VENDOR; /* set new context: START -> VENDOR */
break;
case VENDOR: /* VENDOR has 3 Bytes */
if (!isalpha(byte)) goto error; /* Vendor ID needs to be alpha */
vendor[byte_iterator++] = byte; /* read next byte */
if (byte_iterator >= 3) { /* stop after 3rd byte */
vendor[byte_iterator] = '\0'; /* termination */
byte_iterator = 0; /* reset byte counter */
context = BAUDRATE; /* set new context: VENDOR -> BAUDRATE */
}
break;
case BAUDRATE: /* BAUDRATE consists of 1 char only */
baudrate = byte;
context = IDENTIFICATION; /* set new context: BAUDRATE -> IDENTIFICATION */
byte_iterator = 0;
break;
case IDENTIFICATION: /* IDENTIFICATION has 16 bytes */
if (byte == '\r' || byte == '\n') { /* detect line end */
identification[byte_iterator] = '\0'; /* termination */
context = OBIS_CODE; /* set new context: IDENTIFICATION -> OBIS_CODE */
byte_iterator = 0;
}
else identification[byte_iterator++] = byte;
break;
case START_LINE:
break;
case OBIS_CODE:
if ((byte != '\n') && (byte != '\r'))
{
if (byte == '(') {
obis_code[byte_iterator] = '\0';
byte_iterator = 0;
context = VALUE;
}
else obis_code[byte_iterator++] = byte;
}
break;
case VALUE:
if (byte == '*' || byte == ')') {
value[byte_iterator] = '\0';
byte_iterator = 0;
if (byte == ')') {
unit[0] = '\0';
context = END_LINE;
}
else {
context = UNIT;
}
}
else value[byte_iterator++] = byte;
break;
case UNIT:
if (byte == ')') {
unit[byte_iterator] = '\0';
byte_iterator = 0;
context = END_LINE;
}
else unit[byte_iterator++] = byte;
break;
case END_LINE:
if (byte == '\r' || byte == '\n') {
/* 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);
byte_iterator = 0;
number_of_tuples++;
context = OBIS_CODE;
}
}
break;
case END:
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;
}
}
error:
print(log_error, "Something unexpected happened: %s:%i!", mtr, __FUNCTION__, __LINE__);
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;
}