/*
* modbuslog
*
* Copyright (C) 2011 Jonathan McCrohan
*
* This program 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
* (at your option) any later version.
* This program 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 this program. If not, see .
*/
// gcc modbuslog.c -o modbuslog `pkg-config --libs --cflags libmodbus libconfig`
#ifndef VERSION_STRING
#define VERSION_STRING "[undefined version]"
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// handle SIGALRM by resetting it
void minute_check(int signum) {
alarm(60);
}
// get mac address of primary interface eth0
char *mac_address() {
int s;
struct ifreq ifr;
s = socket(PF_INET, SOCK_DGRAM, 0);
memset(&ifr, 0x00, sizeof(ifr));
strcpy(ifr.ifr_name, "eth0");
ioctl(s, SIOCGIFHWADDR, &ifr);
close(s);
static char mac_address[12];
sprintf(mac_address, "%.2X%.2X%.2X%.2X%.2X%.2X",
(unsigned char) ifr.ifr_hwaddr.sa_data[0],
(unsigned char) ifr.ifr_hwaddr.sa_data[1],
(unsigned char) ifr.ifr_hwaddr.sa_data[2],
(unsigned char) ifr.ifr_hwaddr.sa_data[3],
(unsigned char) ifr.ifr_hwaddr.sa_data[4],
(unsigned char) ifr.ifr_hwaddr.sa_data[5]);
return mac_address;
}
int main(int argc, char *argv[]) {
int DEBUG = 0;
int SYSLOG_CONSOLE_OUTPUT = 0;
int k;
// check the argv array for strings matching -d
for (k = 1; k < argc; k++) {
if (strcmp(argv[k], "-d") == 0) {
DEBUG = 1;
SYSLOG_CONSOLE_OUTPUT = LOG_PERROR;
}
}
openlog("modbuslog", SYSLOG_CONSOLE_OUTPUT | LOG_PID | LOG_CONS, LOG_USER);
syslog(LOG_INFO, "");
syslog(LOG_INFO, "modbuslog [%s] starting", VERSION_STRING);
syslog(LOG_INFO, "");
const char *configfile = "/etc/modbuslog.cfg";
config_t cfg;
//config_setting_t *setting;
const char *modbus_device_address;
int modbus_baud_rate;
int modbus_data_bits;
const char *modbus_parity;
int modbus_stop_bits;
int modbus_retry;
config_init(&cfg);
// attempt to read config
// loads entire file to memory and destroys file descriptor
if (!config_read_file(&cfg, configfile)) {
fprintf(stderr, "%s:%d - %s\n", config_error_file(&cfg),
config_error_line(&cfg), config_error_text(&cfg));
config_destroy(&cfg);
syslog(LOG_ERR, "Unable to find configfile");
return -1;
} else {
syslog(LOG_INFO, "configfile found successfully");
}
// die if core config file options aren't there
if (!(config_lookup_string(&cfg, "modbus.device", &modbus_device_address)
&& config_lookup_int(&cfg, "modbus.baud", &modbus_baud_rate)
&& config_lookup_int(&cfg, "modbus.data_bits", &modbus_data_bits)
&& config_lookup_string(&cfg, "modbus.parity", &modbus_parity)
&& config_lookup_int(&cfg, "modbus.stop_bits", &modbus_stop_bits)
&& config_lookup_int(&cfg, "modbus.retry", &modbus_retry))) {
syslog(LOG_ERR,
"Incomplete modbus configuration. Check configuration file");
closelog();
return -1;
}
syslog(LOG_INFO, "MAC=[%s]", mac_address());
syslog(LOG_INFO, "Serial Port=[%s]", modbus_device_address);
syslog(LOG_INFO, "Serial Port Parameters=[%d %d %c %d]", modbus_baud_rate,
modbus_data_bits, modbus_parity[0], modbus_stop_bits);
modbus_t *ctx;
uint16_t tab_reg[4];
uint8_t byte[8];
int rc;
int i;
// used to force a reading 2 mins after power on
int firstrun;
firstrun = 1;
// SIGALRM used to wake for loop up every 60 secs
// sleep puts whole thread to sleep which isn't what we want
// other methods involve CPU spinlocks which are inefficient
signal(SIGALRM, minute_check);
alarm(60);
for (;;) {
// block until SIGARLM
select(0, NULL, NULL, NULL, NULL);
time_t t = time(NULL);
struct tm unixtime_min_time_t = *gmtime (&t);
unixtime_min_time_t.tm_sec = 0;
time_t unixtime_min = mktime(&unixtime_min_time_t);
//int unixtime = (int) t;
//printf("%d\n", unixtime);
config_setting_t *readings;
readings = config_lookup(&cfg, "reading");
// find number of required readings
unsigned int num_readings = config_setting_length(readings);
int i;
// cycle through each reading and pull info from config file
for (i = 0; i < num_readings; ++i) {
config_setting_t *register_element = config_setting_get_elem(
readings, i);
int slaveid;
config_setting_lookup_int(register_element, "slaveid", &slaveid);
int intervalvalue;
config_setting_lookup_int(register_element, "intervalvalue",
&intervalvalue);
//printf("%d", intervalvalue);
int registertype;
config_setting_lookup_int(register_element, "registertype",
®istertype);
int startbyte;
config_setting_lookup_int(register_element, "startbyte",
&startbyte);
int numbytes;
config_setting_lookup_int(register_element, "numbytes",
&numbytes);
int signedvalue;
config_setting_lookup_int(register_element, "signedvalue",
&signedvalue);
const char *intervalunit;
config_setting_lookup_string(register_element, "intervalunit",
&intervalunit);
//printf("%c", intervalunit[0]);
int intervalduration;
if (intervalunit[0] == 'h')
intervalduration = intervalvalue * 3600;
else
intervalduration = intervalvalue * 60;
// if we match the required time for the reading or first run
if ((unixtime_min % intervalduration == 0) || firstrun) {
//printf("specified minute. %d %d\n", unixtime_min,
// intervalduration);
// attempt to create new modbus connection
ctx = modbus_new_rtu(modbus_device_address, modbus_baud_rate,
modbus_parity[0], modbus_data_bits, modbus_stop_bits);
if (ctx == NULL) {
syslog(LOG_ERR, "Unable to create libmodbus object");
}
modbus_set_slave(ctx, slaveid);
modbus_set_debug(ctx, DEBUG);
if (modbus_connect(ctx) == -1) {
syslog(LOG_ERR, "libmodbus: Connection failed: [%s]",
modbus_strerror(errno));
modbus_free(ctx);
// if connection fails, skip rest of loop
syslog(
LOG_ERR,
"Skipping read from slaveid=[%i], registertype=[%i], startbyte=[%i], numbytes=[%i]",
slaveid, registertype, startbyte, numbytes);
continue;
}
int retry = 0;
// zero tab_reg before use
memset(tab_reg, 0, sizeof(tab_reg));
// convert bytes to registers
int nummodbusregisters;
nummodbusregisters = (((numbytes-1)/2)+1);
int startmodbusregister;
startmodbusregister = (startbyte/2);
// handle timeouts and retries
do {
switch (registertype) {
case 3:
rc = modbus_read_registers(ctx, startmodbusregister,
nummodbusregisters, tab_reg);
break;
case 4:
rc = modbus_read_input_registers(ctx, startmodbusregister,
nummodbusregisters, tab_reg);
break;
default:
rc = modbus_read_registers(ctx, startmodbusregister,
nummodbusregisters, tab_reg);
break;
}
if (rc == -1) {
syslog(LOG_ERR, "libmodbus error: [%s]",
modbus_strerror(errno));
// back off for 2 secs, increasing by 2 for each
// incorrect reply
sleep(2+retry*2);
// open serial port file descriptor,
// flush IO, write zero-ouput, reflush IO,
// close file descriptor
int fd = open(modbus_device_address, O_RDWR | O_NOCTTY );
if (tcflush(fd, TCIOFLUSH) == 0){
syslog(LOG_WARNING, "Flushing [%s] serial IO buffer", modbus_device_address);
}
if (tcsendbreak(fd, 0) == 0){
syslog(LOG_WARNING, "Writing zero-value bits to [%s]", modbus_device_address);
}
if (tcflush(fd, TCIOFLUSH) == 0){
syslog(LOG_WARNING, "Reflushing [%s] serial IO buffer", modbus_device_address);
}
close(fd);
}
retry++;
} while ((rc == -1) && (retry <= modbus_retry));
if (rc == -1){
// retried, but still no luck
modbus_close(ctx);
modbus_free(ctx);
continue;
}
//MODBUS_GET_HIGH_BYTE(data);
//MODBUS_GET_LOW_BYTE(data);
//MODBUS_SET_INT16_TO_INT8(tab_int8, index, value);
// round forward to next midnight
struct tm midnight = *localtime(&t);
midnight.tm_hour = 0;
midnight.tm_min = 0;
midnight.tm_sec = 0;
midnight.tm_mday += 1;
mktime (&midnight);
char log_filename[50];
//_YYYY_MM_DD_HH_MM_SS.log
sprintf(log_filename,
"/var/modbuslog/%s_%04i_%02i_%02i_%02i_%02i_%02i.log",
mac_address(), midnight.tm_year + 1900,
midnight.tm_mon + 1, midnight.tm_mday, midnight.tm_hour,
midnight.tm_min, midnight.tm_sec);
//printf("%s\n",filename);
FILE *fp = fopen(log_filename, "r");
if (fp) {
fclose(fp);
} else {
// file doesn't exist. create it.
syslog(LOG_NOTICE, "logfile does not exist");
syslog(LOG_INFO, "creating file: [%s]", log_filename);
FILE *fp = fopen(log_filename, "w");
fprintf(
fp,
"IntervalID|UTCDate|UTCTime|LOCALDate|LOCALTime|SensorID|RegisterID|Reading\n");
fclose(fp);
}
FILE *filehandle = fopen(log_filename, "a+");
syslog(LOG_DEBUG, "opening file for append: [%s]",
log_filename);
uint32_t registervalue;
int p;
// zero byte before use
memset(byte, 0, sizeof(byte));
// data arrives in words, split into bytes
for (p = 0; p < nummodbusregisters; p++) {
MODBUS_SET_INT16_TO_INT8(byte, p * 2, tab_reg[p]);
//registervalue += tab_reg[i] + tab_reg[i + 1];
}
// initialise register value
// need to check if 2s complement
if (signedvalue && (byte[0]>>7)){
registervalue = 0xffffffff;
} else{
registervalue = 0;
}
// bitshifting magic
switch (numbytes) {
case 1:
registervalue <<= 8;
registervalue |= ((startbyte % 2 == 0) ? byte[0] : byte[1]);
break;
case 2:
registervalue <<= 16;
registervalue |= (((byte[0]) << 8) + byte[1]);
break;
case 4:
registervalue <<= 24;
registervalue |= (((byte[0]) << 24) + ((byte[1]) << 16)
+ ((byte[2]) << 8) + byte[3]);
break;
default:
registervalue <<= 16;
registervalue |= (((byte[0]) << 8) + byte[1]);
break;
}
struct tm utc = *gmtime(&unixtime_min);
struct tm lc = *localtime(&unixtime_min);
int intervalid;
char interval_filename[50];
sprintf(interval_filename,
"/var/modbuslog/interval/interval.txt");
syslog(LOG_DEBUG, "opening interval file: [%s]",
interval_filename);
FILE *intervalfile = fopen(interval_filename, "r+");
if (intervalfile) {
fclose(intervalfile);
} else {
// file doesn't exist. create it.
syslog(LOG_NOTICE, "interval file does not exist");
syslog(LOG_INFO, "attempting to create file: [%s]",
interval_filename);
intervalfile = fopen(interval_filename, "w");
fprintf(intervalfile, "0\n");
fclose(intervalfile);
}
// file now exists, try opening again
intervalfile = fopen(interval_filename, "r+");
fscanf(intervalfile, "%d", &intervalid);
//handle 32bit signed overflow
if (intervalid >= 2147483647) {
intervalid = 0;
} else {
intervalid++;
}
rewind(intervalfile);
fprintf(intervalfile, "%d", intervalid);
fclose(intervalfile);
if (signedvalue){
fprintf(
filehandle,
"%i|%04i%02i%02i|%02i%02i%02i|%04i%02i%02i|%02i%02i%02i|%i|%i|%i\n",
intervalid, utc.tm_year + 1900, utc.tm_mon + 1,
utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec,
lc.tm_year + 1900, lc.tm_mon + 1, lc.tm_mday,
lc.tm_hour, lc.tm_min, lc.tm_sec, slaveid, startbyte,
registervalue);
fclose(filehandle);
syslog(
LOG_DEBUG,
"[%i|%04i%02i%02i|%02i%02i%02i|%04i%02i%02i|%02i%02i%02i|%i|%i|%i]\n",
intervalid, utc.tm_year + 1900, utc.tm_mon + 1,
utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec,
lc.tm_year + 1900, lc.tm_mon + 1, lc.tm_mday,
lc.tm_hour, lc.tm_min, lc.tm_sec, slaveid, startbyte,
registervalue);
} else{
fprintf(
filehandle,
"%i|%04i%02i%02i|%02i%02i%02i|%04i%02i%02i|%02i%02i%02i|%i|%i|%u\n",
intervalid, utc.tm_year + 1900, utc.tm_mon + 1,
utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec,
lc.tm_year + 1900, lc.tm_mon + 1, lc.tm_mday,
lc.tm_hour, lc.tm_min, lc.tm_sec, slaveid, startbyte,
registervalue);
fclose(filehandle);
syslog(
LOG_DEBUG,
"[%i|%04i%02i%02i|%02i%02i%02i|%04i%02i%02i|%02i%02i%02i|%i|%i|%u]\n",
intervalid, utc.tm_year + 1900, utc.tm_mon + 1,
utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec,
lc.tm_year + 1900, lc.tm_mon + 1, lc.tm_mday,
lc.tm_hour, lc.tm_min, lc.tm_sec, slaveid, startbyte,
registervalue);
}
modbus_close(ctx);
modbus_free(ctx);
sleep(1);
//return 0;
}
//printf("%d ", slaveid);
}
// revert to normal timing
firstrun = 0;
//printf("%d\n", unixtime_min);
}
}