int main(int ac, char *av[]) { int status = 0; char* logfname = 0; /// Data directory, conffile paths may be relative const char* datadir; int interval = DEFAULT_INTERVAL; prod_spec spec; prod_class_t clss; int toffset = TOFFSET_NONE; int loggingToStdErr = 0; unsigned queue_size = 5000; const char* progname = basename(av[0]); unsigned logopts = LOG_CONS|LOG_PID; /* * Setup default logging before anything else. */ (void)log_init(progname); const char* pqfname = getQueuePath(); spec.feedtype = DEFAULT_FEEDTYPE; spec.pattern = DEFAULT_PATTERN; if(set_timestamp(&clss.from)) /* corrected by toffset below */ { int errnum = errno; log_error("Couldn't set timestamp: %s", strerror(errnum)); exit(EXIT_FAILURE); /*NOTREACHED*/ } clss.to = TS_ENDT; clss.psa.psa_len = 1; clss.psa.psa_val = &spec; /* * deal with the command line, set options */ { extern int optind; extern int opterr; extern char *optarg; int ch; int fterr; opterr = 1; while ((ch = getopt(ac, av, "vxel:d:f:q:o:p:i:t:")) != EOF) { switch (ch) { case 'v': if (!log_is_enabled_info) (void)log_set_level(LOG_LEVEL_INFO); break; case 'x': (void)log_set_level(LOG_LEVEL_DEBUG); break; case 'e': key = ftok("/etc/rc.d/rc.local",'R'); semkey = ftok("/etc/rc.d/rc.local",'e'); shmid = shmget(key, sizeof(edex_message) * queue_size, 0666 | IPC_CREAT); semid = semget(semkey, 2, 0666 | IPC_CREAT); break; case 'l': logfname = optarg; (void)log_set_destination(logfname); break; case 'd': setPqactDataDirPath(optarg); break; case 'f': fterr = strfeedtypet(optarg, &spec.feedtype); if(fterr != FEEDTYPE_OK) { log_error("Bad feedtype \"%s\", %s\n", optarg, strfeederr(fterr)); usage(progname); } break; case 'q': pqfname = optarg; break; case 'o': toffset = atoi(optarg); if(toffset == 0 && *optarg != '0') { log_error("invalid offset %s\n", optarg); usage(progname); } break; case 'i': interval = atoi(optarg); if(interval == 0 && *optarg != '0') { log_error("invalid interval %s\n", optarg); usage(progname); } break; case 't': pipe_timeo = atoi(optarg); if(pipe_timeo == 0 && *optarg != 0) { log_error("invalid pipe_timeo %s", optarg); usage(progname); } break; case 'p': spec.pattern = optarg; break; default: usage(progname); break; } } conffilename = getPqactConfigPath(); datadir = getPqactDataDirPath(); { int numOperands = ac - optind; if (1 < numOperands) { log_error("Too many operands"); usage(progname); } else if (1 == numOperands) { conffilename = av[optind]; } } } setQueuePath(pqfname); log_notice("Starting Up"); if ('/' != conffilename[0]) { /* * The pathname of the configuration-file is relative. Convert it * to absolute so that it can be (re)read even if the current * working directory changes. */ #ifdef PATH_MAX char buf[PATH_MAX]; /* includes NUL */ #else char buf[_POSIX_PATH_MAX]; /* includes NUL */ #endif if (getcwd(buf, sizeof(buf)) == NULL) { log_syserr("Couldn't get current working directory"); exit(EXIT_FAILURE); } (void)strncat(buf, "/", sizeof(buf)-strlen(buf)-1); (void)strncat(buf, conffilename, sizeof(buf)-strlen(buf)-1); conffilename = strdup(buf); if (conffilename == NULL) { log_syserr("Couldn't duplicate string \"%s\"", buf); exit(EXIT_FAILURE); } } /* * Initialize the previous-state module for this process. */ if (stateInit(conffilename) < 0) { log_error("Couldn't initialize previous-state module"); exit(EXIT_FAILURE); /*NOTREACHED*/ } /* * Configure the standard I/O streams for execution of child processes. */ if (configure_stdio_file_descriptors()) { log_error("Couldn't configure standard I/O streams for execution " "of child processes"); exit(EXIT_FAILURE); } /* * Inform the "filel" module about the number of available file * descriptors. File descriptors are reserved for stdin, stdout, * stderr, the product-queue, the configuration-file, and (possibly) * logging. */ if (0 != set_avail_fd_count(openMax() - 6)) { log_error("Couldn't set number of available file-descriptors"); log_notice("Exiting"); exit(EXIT_FAILURE); /*NOTREACHED*/ } /* * Inform the "filel" module of the shared memory segment */ if (shmid != -1 && semid != -1) { set_shared_space(shmid, semid, queue_size); } /* * Compile the pattern. */ if (re_isPathological(spec.pattern)) { log_error("Adjusting pathological regular-expression: \"%s\"", spec.pattern); re_vetSpec(spec.pattern); } status = regcomp(&spec.rgx, spec.pattern, REG_EXTENDED|REG_NOSUB); if(status != 0) { log_error("Can't compile regular expression \"%s\"", spec.pattern); log_notice("Exiting"); exit(EXIT_FAILURE); /*NOTREACHED*/ } /* * register exit handler */ if(atexit(cleanup) != 0) { log_syserr("atexit"); log_notice("Exiting"); exit(EXIT_FAILURE); /*NOTREACHED*/ } /* * set up signal handlers */ set_sigactions(); /* * Read in (compile) the configuration file. We do this first so * its syntax may be checked without opening a product queue. */ if ((status = readPatFile(conffilename)) < 0) { exit(EXIT_FAILURE); /*NOTREACHED*/ } else if (status == 0) { log_notice("Configuration-file \"%s\" has no entries. " "You should probably not start this program instead.", conffilename); } /* * Open the product queue */ status = pq_open(pqfname, PQ_READONLY, &pq); if(status) { if (PQ_CORRUPT == status) { log_error("The product-queue \"%s\" is inconsistent\n", pqfname); } else { log_error("pq_open failed: %s: %s\n", pqfname, strerror(status)); } exit(EXIT_FAILURE); /*NOTREACHED*/ } if(toffset != TOFFSET_NONE) { /* * Filter and queue position set by "toffset". */ clss.from.tv_sec -= toffset; pq_cset(pq, &clss.from); } else { bool startAtTailEnd = true; timestampt insertTime; clss.from = TS_ZERO; /* * Try getting the insertion-time of the last, * successfully-processed data-product from the previous session. */ status = stateRead(&insertTime); if (status) { log_warning("Couldn't get insertion-time of last-processed " "data-product from previous session"); } else { timestampt now; (void)set_timestamp(&now); if (tvCmp(now, insertTime, <)) { log_warning("Time of last-processed data-product from previous " "session is in the future"); } else { char buf[80]; (void)strftime(buf, sizeof(buf), "%Y-%m-%d %T", gmtime(&insertTime.tv_sec)); log_notice("Starting from insertion-time %s.%06lu UTC", buf, (unsigned long)insertTime.tv_usec); pq_cset(pq, &insertTime); startAtTailEnd = false; } } if (startAtTailEnd) { log_notice("Starting at tail-end of product-queue"); (void)pq_last(pq, &clss, NULL); } }
/********************************************************************* * * MAIN process * ********************************************************************/ int main(int argc, char* argv[]) { pid_t pid; int num_counters=0; int last_counter; int i, k; PIN_STATE_t pin_value; PIN_STATE_t active_value=LOW; struct timespec pulse_start_time, pulse_end_time; unsigned char pulse_started; /* Parse input parameters */ if ((argc==1) || ((argc==2) && !strcmp(argv[1], "-h"))) { printf("Usage:\n"); printf(" pulsecountd <pin1> <div1> [<statefile1>] [<pin2> <div2> [<statefile2>] ... [<pinN> <divN> [<statefileN>]]]\n"); printf(" pinX: kernel Id of GPIO pin to count pulses on (use 0 for dummy pin)\n"); printf(" divX: divisor for pulse count values\n"); printf(" statefileX: file containing the counter selection\n"); printf(" (optional, used for dual virtual counters)\n\n"); printf(" Note: max number of counters is %d\n", MAX_COUNTERS); return 1; } memset(counter_param, 0, sizeof(counter_param)); for (i=1, k=0; i<(argc); i++, k++) { /* Get pin Id and divisor for each counter */ counter_param[k].pin = atoi(argv[i++]); counter_param[k].divisor = atof(argv[i]); /* is this the last parameter? */ if (i<(argc-1)) { /* is next parameter a string (filename)? */ if ((*argv[i+1] < '0') || (*argv[i+1] > '9')) { /* next parameter is the optional statefile name, save it */ strcpy(counter_param[k].statefile, argv[++i]); } } if (counter_param[k].pin) num_counters++; #if 0 printf("counter_param[%d].pin=%d\n", k, counter_param[k].pin); printf("counter_param[%d].divisor=%f\n", k, counter_param[k].divisor); printf("counter_param[%d].statefile=%s\n\n", k, counter_param[k].statefile); #endif } last_counter = k; openlog("pulsecountd", LOG_PID|LOG_CONS, LOG_USER); syslog(LOG_DAEMON | LOG_NOTICE, "Starting Pulse counter daemon (version %s)", VERSION); syslog(LOG_DAEMON | LOG_NOTICE, "Using %s logic", (active_value==HIGH)?"ACTIVE_HIGH":"ACTIVE_LOW" ); /* Install signal handler for SIGTERM and SIGINT ("CTRL C") * to be used to cleanly terminate parent annd child processes */ signal(SIGTERM, doExit); signal(SIGINT, doExit); /* Save the parent pid (used in signal handler) */ parentpid = getpid(); /* Init counter struct */ memset((void*)&counter, 0, sizeof(counter)); syslog(LOG_DAEMON | LOG_NOTICE, "Creating %d counter processes", num_counters); /* Create a child process for each counter */ for (i=0; i<last_counter; i++) { /* Check for dummy pin */ if (counter_param[i].pin == 0) continue; /* Create child */ pid=fork(); if (pid == -1) { syslog(LOG_DAEMON | LOG_ERR, "Error creating child process for counter %d", i+1); return 2; } if (pid != 0) { /***** We are in the parent process *****/ /* Save child pid */ counter_param[i].child_pid=pid; } else { /***** We are in the child process *****/ syslog(LOG_DAEMON | LOG_NOTICE, "Started child process counting pulses on GPIO pin with Kernel Id %d", counter_param[i].pin); /* Init */ if (setup(counter_param[i], &counter) != 0) { /* Setup failed, wait to be terminated */ while (1) usleep(100000); } /* Wait for possibly ongoing pulse to end */ while (digitalRead(counter.value_fd) == active_value) usleep(100000); pulse_started = 0; /***** Main child loop *****/ while (1) { if (waitForEdge(&pin_value, counter.value_fd) == 0) { if (pin_value == active_value) { /* Pulse started */ if (pulse_started == 0) { clock_gettime(CLOCK_REALTIME, &pulse_start_time); pulse_started = 1; } else { #if DEBUG syslog(LOG_DAEMON | LOG_DEBUG, "Warning: detected starting pulse out of sequence on pin %d", counter_param[i].pin); #endif } } else { char str[20]; /* Pulse ended */ if (pulse_started == 1) { clock_gettime(CLOCK_REALTIME, &pulse_end_time); #if DEBUG syslog(LOG_DAEMON | LOG_DEBUG, "Detected pulse with length %lu ms on pin %d", time_diff_ms(pulse_end_time, pulse_start_time), counter_param[i].pin); #endif pulse_started = 0; } else { #if DEBUG syslog(LOG_DAEMON | LOG_DEBUG, "Warning: detected ending pulse out of sequence on pin %d", counter_param[i].pin); #endif continue; } /* TODO: check pulse lenght and filter glitches */ /* Check statefile for which virtual counter to increment */ switch (stateRead(counter.state_fd)) { case 1: /* Increment counter 1, apply divisor and save value */ counter.pulse_count1++; sprintf(str, "%010lu\n", (unsigned long)(counter.pulse_count1/counter.divisor)); pwrite(counter.export1_fd, str, strlen(str), 0); break; case 2: /* Increment counter 2, apply divisor and save value */ counter.pulse_count2++; sprintf(str, "%010lu\n", (unsigned long)(counter.pulse_count2/counter.divisor)); pwrite(counter.export2_fd, str, strlen(str), 0); break; default: /* unknown state, do nothing */ ; //printf("unknown value %d in statefile\n", stateRead(counter.state_fd)); } } } else { /* waitForEdge failed, wait to be terminated */ while (1) usleep(100000); } } } } /* Create child process for TCP server */ modbus_server_pid=fork(); if (modbus_server_pid == -1) { syslog(LOG_DAEMON | LOG_ERR, "Error creating child process for Modbus server"); return 3; } if (modbus_server_pid == 0) { /***** We are in the child process *****/ modbus_server_pid = getpid(); /* Start Modbus TCP server loop */ modbustcp_server(MODBUS_SLAVE_ADDRESS, // Modbus slave address read_register_handler, // Read register handler NULL // Write register handler ); } else { /***** We are in the parent process *****/ /* Wait for termination signal */ while (cont) sleep(1); } /* Wait for all counter child processes to terminate */ for (i=0; i<last_counter; i++) { /* Check for dummy pin */ if (counter_param[i].pin == 0) continue; kill(counter_param[i].child_pid, SIGTERM); if (waitpid(counter_param[i].child_pid, NULL, 0) == counter_param[i].child_pid) { syslog(LOG_DAEMON | LOG_NOTICE, "Child process %d successfully terminated", counter_param[i].child_pid); } else { syslog(LOG_DAEMON | LOG_ERR, "Error terminating child process %d", counter_param[i].child_pid); } } /* Terminate Modbus server */ kill(modbus_server_pid, SIGTERM); if (waitpid(modbus_server_pid, NULL, 0) == modbus_server_pid) { syslog(LOG_DAEMON | LOG_NOTICE, "Modbus server process %d successfully terminated", modbus_server_pid); } else { syslog(LOG_DAEMON | LOG_ERR, "Error terminating Modbus server process %d", modbus_server_pid); } syslog(LOG_DAEMON | LOG_NOTICE, "Exiting Pulse counter daemon"); closelog(); return 0; }