/* * 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); } }