Nahajate se tukaj

Electricity meter

/* Reading electricity meter Landis+Gyr ZMF 120AC impulses for RRDtool logging Branje impulzov iz LandisGyr stevca ZMF 120AC po RS232 protokolu. Konstanta stevca 500 imp/kWh. Dolzina impulza 40ms Leon Kos, 2011 RS232 adapter with 20 meters of phone cable between meter and Linux server requires two components: R1 provides pulldown. D1 reverse voltage protection. DB9 RS232 Connector ___ TxD | 3 |---+ | | | | | | | R1 | | | | 1K5 | | | | | | | blue/white RxD | 2 |---+----------------------+ | | | ====|==== | | --- D1 |1o o3 o4| 6 pin connector in ZMF120AC meter | | \ / BAT85 | | to four optocouplers SFH6156 | | ===== |2o o4 o5| | | | blue ====|==== DTR | 4 |---+----------------------+ --- Electricity meter ZMF 120AC pin descriptions: Pin 1: Input 1 Anode Pin 2: Input 1 Catode Pin 3: Signal ground = common Emitor and Catode for optocouplers 2,3,4 Pin 4: Output 2 Collector Pin 5: Input 3 Anode Pin 6: Output 4 Collector Open collector pulses between pins 4 and 3: T=variable pulse frequency with meter constant C=500imp/kWh __ _______________ _______________ _______________ ____ | | | | | | | | | | | | | | | | +-+ +-+ +-+ +-+ T_pulse=40ms Absolute maximum ratings for optocouplers SFH6156: Diode: I_F=60mA, V_reverse = 6V transistor: V_CE=70V, V_CEO=7V, I_C=50mA Daemon description: This is standard linux daemon that reads impulses as it were RS232 chars. Every impulse is read and added to counter that is split into two tariffs for better accounting of total energy consumption. As T_pulse is constant characters are read at 150baud giving 0x60 reads with 7bit char length. When enough time is passes (5 minutes) counters reading is send to the remote RRDcollect daemon that stores reading into RRD two databases created with: # Measuring electric power in kW # Create 5 min samples for 8 days, hourly samples for 32 days # and dayly samples for 5 years rrdtool create --no-overwrite data/electricity-meter-counter.rrd \ DS:PWR:COUNTER:900:0:4294967295 \ RRA:AVERAGE:0.5:1:2304 \ RRA:AVERAGE:0.5:12:768 \ RRA:AVERAGE:0.5:288:1890 \ RRA:MAX:0.5:12:768 \ RRA:MAX:0.5:288:1890 \ RRA:MIN:0.5:12:768 \ RRA:MIN:0.5:288:1890 # Recording energy in kWh # Create hourly samples for one month and daily samples for 5 years rrdtool create --no-overwrite data/electricity-meter-gauge.rrd \ DS:NT:GAUGE:900:0:4294967295 \ DS:VT:GAUGE:900:0:4294967295 \ RRA:MAX:0.5:12:744 \ RRA:MAX:0.5:288:1890 Reading are loged into syslog for backup purposes and for restoring counter values between server downtime. New counter values at reboot are estimated from past two syslog entries. There are no arguments required as all parameters are baked in. Except optional initialization consumption values that is used to synchronize meter readings at first run or possible correction. Signal SIGUSR1 forces current meter readings to syslog output. Compile the daemon with: cc -Wall -lrrd electricity-meter.c -o electricity-meter For RRD database monitor visit http://mon.hpc.fs.uni-lj.si References: - Meter "technical data": http://www.landisgyr.eu/apps/products/data/pdf1/LandisGyr_ZMF100AC_TechD... - RRDcollect usage: http://www.netways.de/uploads/media/Sebastian_Harl_How_to_Escape_the_IO_... - Daemon writing: http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HAVE_STDINT_H 1 #include #include #define SERIAL_DEVICE "/dev/ttyS0" /* RS232 port */ #define BUFFER_SIZE 10 /* Size of read buffer */ #define RRD_INTERVAL 300 /* RRD logging interval in seconds */ #define SYSLOG_FILE "/var/log/messages" /* for counter restore */ #define SYSLOG_IDENT "electricity-meter" /* facility name */ #define SYSLOG_INTERVAL 3600 /* Log interval into syslog */ #define METER_CONSTANT 500 /* Energy meter frequency impulses/kWh */ #define RRDCACHED_ADDRESS "10.0.2.1" typedef uint32_t counter; static counter tariff_counter[2]; /* Pulse counter */ static sig_atomic_t terminate_signal_received; /* Loop terminate */ static sig_atomic_t log_counter_requested; static void signal_handler( int signal ) { switch( signal ) { case SIGUSR1: /* SGE send this begore SIGSTOP */ log_counter_requested = 1; break; case SIGHUP: case SIGTERM: case SIGINT: terminate_signal_received = 1; log_counter_requested = 1; // fprintf(stderr, "Terminating...\n"); break; } } /* Local policy on tariffs (low and high). */ static int current_tariff() { struct tm *tm; time_t now = time(0); tm = localtime(&now); if (tm->tm_wday == 0 || tm->tm_wday == 6 /*Saturday or Sunday*/ || tm->tm_hour >= 22 || tm->tm_hour < 6) /* From 22 to 6 */ return 0; return 1; } /* If constant load is assumed then one week contains 2*24+5*8=88 hours of Tariff 0 (NT) and 5*16=80 hours of Tarrif 1 (VT) */ static void restore_tariffs_approximately(double kWh) { counter counter; tariff_counter[0] = 0.5238*kWh*METER_CONSTANT; tariff_counter[1] = 0.4762*kWh*METER_CONSTANT; counter = tariff_counter[0] + tariff_counter[1]; syslog(LOG_INFO, "Tariffs approximated using %.1Lf kWh", (long double)counter/METER_CONSTANT); } /* Last two readings are used for restoring counter from syslog */ static int restore_counter_from_syslog() { # define MAX_LENGTH 80 time_t prev_seconds=0, last_seconds=0; counter prev_counter[2], last_counter[2]; char line[MAX_LENGTH]; FILE *f; f = fopen(SYSLOG_FILE, "r"); if (f == NULL) { perror("Error: reading " SYSLOG_FILE); return errno; } while(fgets(line, MAX_LENGTH, f)) { char *facility; if((facility=strstr(line, SYSLOG_IDENT)) && (strstr(line, "imp ==")) ) { int i; prev_seconds = last_seconds; prev_counter[0] = last_counter[0]; prev_counter[1] = last_counter[1]; for(i = 0; i < facility-line; i++) if (line[i] == ':' && line[i+3] == ':') { last_seconds = 3600*atoi(&line[i-2]) + 60*atoi(&line[i+1])+atoi(&line[i+4]); sscanf(facility+sizeof(SYSLOG_IDENT), "%u+%u", &last_counter[0], &last_counter[1]); // printf("%lu+%lu\n",last_counter[0], last_counter[1]); break; } } } fclose(f); if (last_seconds > prev_seconds) /* two readings in correct order */ { float power; double total; struct tm *tm; int tariff; time_t now = time(0); tm = localtime(&now); now = (3600*tm->tm_hour+60*tm->tm_min+tm->tm_sec)%(24*3600); assert(now > last_seconds); power = (float)(last_counter[0]+last_counter[1] -prev_counter[0]-prev_counter[1]) /(last_seconds-prev_seconds); tariff = current_tariff(); tariff_counter[tariff] = last_counter[tariff] + power*(now-last_seconds); if (tariff == 0) tariff_counter[1] = last_counter[1]; else tariff_counter[0] = last_counter[0]; total = (tariff_counter[0] + tariff_counter[1]) /(double)METER_CONSTANT; syslog(LOG_INFO, "Estimated %.1lf kWh from " SYSLOG_FILE, total); return 0; }log_counter_requested = 1; return -1; } int main(int argc, char *argv[]) { struct termios t, origterm; // see man termios - declared as above int rc; int fd; time_t last_syslog_time, last_rrd_update_time, now; /* Our process ID and Session ID */ pid_t pid, sid; /* Fork off the parent process */ pid = fork(); if (pid < 0) { exit(EXIT_FAILURE); } /* If we got a good PID, then we can exit the parent process. */ if (pid > 0) { exit(EXIT_SUCCESS); } /* Change the file mode mask */ umask(0); /* Create a new SID for the child process */ sid = setsid(); if (sid < 0) { /* Log the failure */ exit(EXIT_FAILURE); } /* Change the current working directory */ if ((chdir("/")) < 0) { /* Log the failure */ exit(EXIT_FAILURE); } /* Close out the standard file descriptors */ close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); if (argc > 1) restore_tariffs_approximately(atoi(argv[1])); else restore_counter_from_syslog(); // return 0; fd = open(SERIAL_DEVICE, O_RDONLY | O_NOCTTY); rc = tcgetattr (fd, &t); if (rc < 0) { int tmp; tmp = errno; close(fd); errno = tmp; perror("Error opening " SERIAL_DEVICE); return errno; } cfsetospeed(&t, B150); cfsetispeed(&t, B150); tcgetattr(fd,&t); // Get terminal parameters. origterm = t; // Save original settings. // Set to non-canonical mode, and no RTS/CTS handshaking t.c_iflag &= ~(BRKINT|ICRNL|IGNCR|INLCR|INPCK|ISTRIP|IXON|IXOFF|PARMRK); t.c_iflag |= IGNBRK|IGNPAR; t.c_oflag &= ~(OPOST); t.c_cflag &= ~(CRTSCTS|CSIZE|PARENB); t.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON|IEXTEN|ISIG); /* Read some reasonable number bytes at a time, no timeout */ t.c_cc[VMIN] = BUFFER_SIZE; t.c_cc[VTIME] = 0; /* 7 data bits, Receiver enabled, Hangup, Dont change "owner" */ t.c_cflag |= CS7 | CREAD | HUPCL | CLOCAL; rc = tcsetattr(fd, TCSAFLUSH, &t); /* Flush the input and output buffers */ tcflush(fd,TCIOFLUSH); if (rc < 0) { int tmp; tmp = errno; close(fd); errno = tmp; perror("Nastavljanje serijskega porta"); return errno; } fcntl(fd, F_SETFL, 0); /* Normal (blocking) behavior with read() */ signal( SIGUSR1, signal_handler ); signal( SIGHUP, signal_handler ); signal( SIGTERM, signal_handler ); signal( SIGINT, signal_handler ); openlog(SYSLOG_IDENT, LOG_CONS, LOG_NEWS); syslog(LOG_WARNING, "Counting energy with %d imp/kWh", METER_CONSTANT); terminate_signal_received = 0; log_counter_requested = 1; now = time(0); last_syslog_time = now; last_rrd_update_time = now - RRD_INTERVAL; while (!terminate_signal_received) /* nearly infinite loop */ { int rc; unsigned char buffer[BUFFER_SIZE]; char update[25]; const char *values[1] = {update}; tariff_counter[current_tariff()] += read(fd, buffer, BUFFER_SIZE); now = time(0); if (now >= last_syslog_time + SYSLOG_INTERVAL || log_counter_requested) { double total = (tariff_counter[0] + tariff_counter[1]) /(double)METER_CONSTANT; syslog(LOG_INFO, "%u+%u imp == %.1lf kWh", tariff_counter[0], tariff_counter[1], total); last_syslog_time = now; log_counter_requested = 0; } if (now >= RRD_INTERVAL + last_rrd_update_time) { if ((rc = rrdc_connect(RRDCACHED_ADDRESS)) == 0) { snprintf(update, sizeof(update), "N:%.3f:%.3f", (double)tariff_counter[0]/METER_CONSTANT, (double)tariff_counter[1]/METER_CONSTANT); rc = rrdc_update("electricity-meter-gauge.rrd", 1, values); if (rc) { char buffer[100]; snprintf(buffer, sizeof(buffer), "Error updating: %s", rrd_get_error()); syslog(LOG_WARNING, buffer); } snprintf(update, sizeof(update), "N:%u", tariff_counter[0] + tariff_counter[1]); rc = rrdc_update("electricity-meter-counter.rrd", 1, values); if (rc) { char buffer[100]; snprintf(buffer, sizeof(buffer), "Error updating: %s", rrd_get_error()); syslog(LOG_WARNING, buffer); } rrdc_disconnect (); } else { char buffer[120]; snprintf(buffer, sizeof(buffer), "Error: %s @" RRDCACHED_ADDRESS, rrd_get_error()); syslog(LOG_WARNING, buffer); } last_rrd_update_time = now; } } closelog(); // Closes the connection to the port tcsetattr(fd, TCSAFLUSH, &origterm); // restore tty settings tcflush(fd, TCIOFLUSH); close(fd); return 0; }
PriponkaVelikost
PDF icon adapter-LandysGyr-ZMF120AC-RS232.pdf18.08 KB