Solved Polling a sysctl value

I want to start logging on my RaspberryPi2 the values of my DS1820 temp sensor attached to GPIO pins.
They return a value when queried:
Code:
root@rpi2B:~ # sysctl dev.ow_temp.0.temperature
dev.ow_temp.0.temperature: 21.125C

I have setup a RRDTools database for usage.

The tutorial I used showed a python script for polling the DS1820.
https://www.mkompf.com/weather/pionewiremini.html

What I am looking for is suggestions for other methods of polling a sysctl.
I have messed with sysctlbyname function in 'C' so this might be another method.
Looking at the python script I wonder why I couldn't use a shell script instead.

In the tutorial the method of running the polling script is cron.
What are some of the other ways I could do this?
I want to poll every 15 seconds. Does that sound excessive?
 
Personally, I would write a small python or perl or ruby program, which does the polling. I would use multiple threads, one to handle main and startup/shutdown, and another one that's dedicated to reading from the sensor. Synchronization with locks, data exchange with queues. The data acquisition thread should have a "cadence" mechanism (for taking a measurement at regular intervals), a way to calculate when the next measurement is due, and then just use sleep() to get to that time. When I say "small", just the framework to do all this (with a dummy measurement backend and data processing frontend) is probably several hundred lines of code.

Why so complicated? Because if your projects are like my projects, then eventually this little tool will morph into something that has to do many jobs, and at that point you better have good infrastructure.

I have no idea how to interface something like that to RRD, never used it (although it is on my to-do list).

For intervals of 15 seconds, from a performance viewpoint, pretty much anything will work. Cron sounds a bit crazy (it is intended for things that get run every hour, day, week or month), but I think it would actually work fine.
 
Thanks so much. On the cron point I also worry about tying up a vital system component. That is if I crash cron with some malformed script.

The calomel site also uses cron for its shell script.
https://calomel.org/rrdtool.html
(I hate these graphical intro sites)
 
How about this for an idea. Make a sysctl-value 'data collection daemon' that runs on a timer. I could do a script that loops every 15 or 30 seconds.
 
How about a while loop script with sleep 15s for starters.
Code:
root@rpi2B:~ # service dc onestart
Data Collector Working
 
Given that the RPI2 does belong to the group of the least powerful ARM devices, you might be better of going the C way instead of wasting cycles with all the scripting engines.

Some times ago, I published a very tiny C daemon on GitHub for doing a recurring job -- here every 4 seconds, however easily adjustable to other values. Although, the recurrence routine is hard coded, it consist of only 8 simple lines at the end of the daemon program. For a quick start you would change this 8 lines -- for (;;) { ... } -- to something like the following. Here I assume that sysctlbyname(3) for dev.ow_temp.0.temperature returns a double value:

Code:
   FILE *outfile = fopen("/fullpath/to/the/value/table/file", "w");

   double tval = 0.0;
   double tlen = sizeof(double);
 
   for (;;)
   {
      sleep(idle);
      sysctlbyname("dev.ow_temp.0.temperature", &tval, &tlen, NULL, 0);
      fprintf(outfile, "%f\n", tval);
   }
Even it is advertised to run on Mac OS X it runs on FreeBSD out of the box. In order to make this a complete solution, you would want to change the references to the daemon names on lines 20, 22, 23, ... And you would need to fclose() the outfile in the course of signal handling.
 
Well I tried to mash my prior onewire help from this thread:
https://forums.freebsd.org/threads/59220/#post-339801
Into a your daemon program from github.
Here is my file. Can you see anything wrong with the sysyctlbyname section.
https://gist.github.com/frankharvey/252827c2974b12ec716e869f22e27d09

Does my fclose(outfile) look OK? I added 2 #include files.
I never see any output and I am unsure if I should use the /rc.d structure.
Code:
root@rpi2B:/rrdtemp # service rdd_temp -f onestart

usage: rdd_temp [-p file] [-f] [-n] [-t] [-h]
 -p file    the path to the pid file [default: /var/run/rdd_temp.pid]
 -f         foreground mode, don't fork off as a daemon.
 -n         no console, don't fork off as a daemon - started/managed by launchd.
 -t         idle time in seconds, [default: 4 s].
 -h         shows these usage instructions.
I copied the rrd_temp executable to /usr/local/etc/rc.d and made it +x and 755

How (rrd_temp -f) should I be starting this program?
I manually created a /var/run/get_temp.pid and gave it a pid of 665
Also this: touch /rrdtemp/testfile
 
From the forums post which you mentioned, I learned that the result of sysyctlbyname("dev.ow_temp.0.temperature", ...) is a 32bit int in Millikelvin. In order to transform this accurately to Fahrenheit, I would place the received value into a double variable:
Code:
    outfile = fopen("/rrdtemp/testfile", "w");

    size_t size = sizeof(int);
    int    buf;
    double tval;

    for (;;)
    {
       sleep(idle);
       sysctlbyname("dev.ow_temp.0.temperature", &buf, &size, NULL, 0);
       tval = (buf - 273150)*1.8e-3 + 32.0;       // 1e-3 * 9/5 => 1.8e-3
       fprintf(outfile, "%.1f F \n" , tval);      // change 1 to other num of decimal digits if required
    }
The for (;;) loop is supposed to run infinitely, and therefore you must not call return nor fclose() from this loop.

Instead replace line 21 of your code on GitHub with the following:
Code:
FILE *outfile = NULL;
Then replace line 45 with ...
Code:
if (outfile) fclose(outfile);
..., and insert this very conditional call to fclose() before lines 67, 60, and 53.

Placing an executable into the /etc/rc.d does not work. If you want to go the rc-way, then you would need to create a rc(8) script referencing the executable (s. the example in the rc man file). However, for simple daemons I usually use the rc.local facility.

Let's assume, you compiled the executable and placed it at /usr/local/bin/rrd_temp. Then you would place the following line into the file /etc/rc.local -- if it does not exist then create the file:
Code:
/usr/local/bin/rrd_temp -t 15
Then restart. Before this, you want to check whether rrd_temp is working properly using the following sequence:

rrd_temp -t 2
tail -f /rrdtemp/testfile

The file should be populated with new temperature readings every 2 seconds. After some time, break the tail using ctrl-C.

Then kill the rrdtemp daemon with the following command:
kill -HUP `cat /var/run/rrd_temp.pid`
 
Last edited:
In the meantime, I verified the code on one of my FreeBSD x86-64 systems, only using the sysctl "dev.cpu.0.temperature" as input, since I have no one wire T sensor for connecting it. It turned out that everything is almost working just as expected, only we need to fflush() the output after each fprintf(), otherwise the temperature readings are only populated into the file system cache and become written out only on occasions:
Code:
      fprintf(outfile, "%.1f F \n", tval);
      fflush(outfile);
 
I had some trouble with the daemon so I striped it down to the bare basic.
Had to do some reading to figure out the "a" option for fopen. It was writing over the file each time on usage. I assume this means append.
Code:
#include <stdio.h>
#include <sys/types.h>
#include <sys/sysctl.h>

int main()
{
        size_t size=sizeof(int);
        int buf;
        double tval;
        FILE *fp;
        fp = fopen("/get_temp/get_temp.db", "a");
        sysctlbyname("dev.ow_temp.0.temperature", &buf, &size, NULL, 0);
        tval = (buf - 273150)*1.8e-3 + 32.0;
        fprintf(fp,"%.1f \n", tval);
        fflush(fp);
        fclose(fp);
        return 0;
}

Thank You so much for pointing me in the right direction. Learning fprintf in a very practical lesson.

One question: Why did you use 'double' instead of "int' for tval variable data type.
Is this done for digits accuracy? I tried 'int' type and it worked fine.
Seems like 'buf' is working on larger numbers than tval. Just wondering.
 
Went the crontab route for now. This is working so far. Doing every */3 minutes.
So I am polling with cron. It seems to have almost zero footprint.

I was going to add a date timestamp on the output entries but I think i need to migrate the output to rrd update.
That is the end goal.

Looking at time and ctime it looks like an advanced C class lesson.. Not as easy as I had hoped.
t_time data type.. So I am going to skip that and go on to input to rrd database..

I did get to making the skeleton for a rrd graph. Just needs data.

temp_graph.png
 
...
One question: Why did you use 'double' instead of "int' for tval variable data type.
Is this done for digits accuracy? I tried 'int' type and it worked fine.
Seems like 'buf' is working on larger numbers than tval. Just wondering.

The conversion involves floating point arithmetic, and if you do the calculation with integers, then digits after the decimal point become stripped, and if you don't take care the rounding mode won't be the mathematically correct half-up mode.

Example:
Code:
#include <stdio.h>

int main(int argc, const char *argv[])
{
   int    buf  = 304122;
   int    ival = (buf - 273150)*1.8e-3 + 32.0;
   double fval = (buf - 273150)*1.8e-3 + 32.0;
   printf("%5d\n%5.0f\n%7.2f\n", ival, fval, fval);

   return 0;
}
Output:
Code:
   87
   88
   87.75
The correct result is 87.75 °F, and if you store the calculation result in an integer value, the decimal digits are simply stripped, and not rounded half-up, as the correctly rounded value should be 88 °F. You could of course use lround() for forcing the correct rounding mode. Since you are going to feed the results into a graph, it would be better anyway not to strip/round the decimal digits, and the resulting curve would look more smooth, i.e. no staircase appearance.

Went the crontab route for now. This is working so far. Doing every */3 minutes.
So I am polling with cron. It seems to have almost zero footprint.

I was going to add a date timestamp on the output entries ...

I would use a combo of time(3), localtime(3) and strftime(3). See the example below for printing a time stamp in ISO format:
Code:
#include <stdio.h>
#include <time.h>

int main(int argc, const char *argv[])
{
   char   isotime[20];
   time_t stamp = time(NULL);
   strftime(isotime, 20, "%Y-%m-%d %H:%M:%S", localtime(&stamp));
   printf("%s\n", isotime);

   return 0;
}
Output:
Code:
2017-12-08 09:58:03
strftime() is very flexible, you can generate almost every desired date/time format by combining the various format specifiers with white space and punctuation marks (s. the respective man file).
 
Here comes the complete daemon code which I verified to work on my machine, I added a time stamp in ISO format to the log lines:
Code:
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sysctl.h>

#define DAEMON_NAME    "rrd_temp"

const char *pidfname = "/var/run/rrd_temp.pid";
FILE *outfile = NULL;

void usage(const char *executable)
{
   const char *r = executable + strlen(executable);
   while (--r >= executable && *r != '/');
   r++;
   printf("\nusage: %s [-p file] [-f] [-n] [-t] [-h]\n", r);
   printf(" -p file    the path to the pid file [default: /var/run/rrd_temp.pid]\n");
   printf(" -f         foreground mode, don't fork off as a daemon.\n");
   printf(" -n         no console, don't fork off as a daemon - started/managed by launchd.\n");
   printf(" -t         idle time in seconds, [default: 4 s].\n");
   printf(" -h         shows these usage instructions.\n\n");
}


static void signals(int sig)
{
   switch (sig)
   {
      case SIGHUP:
         syslog(LOG_ERR, "Received SIGHUP signal.");
         kill(0, SIGHUP);
         if (outfile) fclose(outfile);
         unlink(pidfname);
         exit(0);
         break;

      case SIGINT:
         syslog(LOG_ERR, "Received SIGINT signal.");
         kill(0, SIGINT);
         if (outfile) fclose(outfile);
         unlink(pidfname);
         exit(0);
         break;

      case SIGQUIT:
         syslog(LOG_ERR, "Received SIGQUIT signal.");
         kill(0, SIGQUIT);
         if (outfile) fclose(outfile);
         unlink(pidfname);
         exit(0);
         break;

      case SIGTERM:
         syslog(LOG_ERR, "Received SIGTERM signal.");
         kill(0, SIGTERM);
         if (outfile) fclose(outfile);
         unlink(pidfname);
         exit(0);
         break;

      default:
         syslog(LOG_ERR, "Unhandled signal (%d) %s", sig, strsignal(sig));
         break;
   }
}


typedef enum
{
   noDaemon,
   launchdDaemon,
   discreteDaemon
} DaemonKind;


void daemonize(DaemonKind kind)
{
   switch (kind)
   {
      case noDaemon:
         openlog(DAEMON_NAME, LOG_NDELAY | LOG_PID | LOG_CONS, LOG_USER);
         break;

      case launchdDaemon:
         signal(SIGTERM, signals);
         openlog(DAEMON_NAME, LOG_NDELAY | LOG_PID, LOG_USER);
         break;

      case discreteDaemon:
      {
         // fork off the parent process
         pid_t 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);

         // The child process continues here.
         // first close all open descriptors
         for (int i = getdtablesize(); i >= 0; --i)
            close(i);

         // re-open stdin, stdout, stderr connected to /dev/null
         int inouterr = open("/dev/null", O_RDWR);    // stdin
         dup(inouterr);                               // stdout
         dup(inouterr);                               // stderr

         // Change the file mode mask, 027 = complement of 750
         umask(027);

         pid_t sid = setsid();
         if (sid < 0)
            exit(EXIT_FAILURE);     // should log the failure before exiting?

         // Check and write our pid lock file
         // and mutually exclude other instances from running
         int pidfile = open(pidfname, O_RDWR|O_CREAT, 0640);
         if (pidfile < 0)
            exit(1);                // can not open our pid file

         if (lockf(pidfile, F_TLOCK, 0) < 0)
            exit(0);                // can not lock our pid file -- was locked already

         // only first instance continues beyound this
         char s[256];
         int  l = snprintf(s, 256, "%d\n", getpid());
         write(pidfile, s, l);      // record pid to our pid file

         signal(SIGHUP,  signals);
         signal(SIGINT,  signals);
         signal(SIGQUIT, signals);
         signal(SIGTERM, signals);
         signal(SIGCHLD, SIG_IGN);  // ignore child
         signal(SIGTSTP, SIG_IGN);  // ignore tty signals
         signal(SIGTTOU, SIG_IGN);
         signal(SIGTTIN, SIG_IGN);

         openlog(DAEMON_NAME, LOG_NDELAY | LOG_PID, LOG_USER);
         break;
      }
   }
}


int main(int argc, char *argv[])
{
   char        ch, *p;
   int64_t     idle  = 4;
   const char *cmd   = argv[0];
   DaemonKind  dKind = discreteDaemon;

   while ((ch = getopt(argc, argv, "p:fnt:h")) != -1)
   {
      switch (ch)
      {
         case 'p':
            pidfname = optarg;
            break;

         case 'f':
            dKind = noDaemon;
            break;

         case 'n':
            dKind = launchdDaemon;
            break;

         case 't':
            if ((idle = strtol(optarg, &p, 10)) <= 0)
            {
               usage(cmd);
               exit(0);
            }
            break;

         case 'h':
         default:
            usage(cmd);
            exit(0);
            break;
      }
   }

   daemonize(dKind);

   outfile = fopen("/tmp/rrd_temp.log", "w");
 
   char   isotime[20];
   time_t stamp;

   size_t size = sizeof(int);
   int    buf;
   double tval;

   for (;;)
   {
      sleep(idle);
      stamp = time(NULL);
      strftime(isotime, 20, "%Y-%m-%d %H:%M:%S", localtime(&stamp));

      sysctlbyname("dev.cpu.0.temperature", &buf, &size, NULL, 0);
      tval = (buf - 2731.5)*1.8e-1 + 32.0;

      fprintf(outfile, "%s\t%.2f °F \n" , isotime, tval);
      fflush(outfile);
   }
}

In order to test it:

clang rrd_temp.c -Wno-empty-body -Ofast -g0 -o rrd_temp
kldload coretemp
./rrd_temp -t 2
tail -f /tmp/rrd_temp.log

Now you should see the logfile being populated with new temperature readings every 2 seconds:
Code:
2017-12-08 10:35:10    66.11 °F
2017-12-08 10:35:12    67.91 °F
2017-12-08 10:35:14    67.91 °F
2017-12-08 10:35:16    67.91 °F
2017-12-08 10:35:18    66.11 °F
2017-12-08 10:35:20    67.91 °F
Break the tail with Ctrl-C. And then stop the daemon:
kill -HUP `cat /var/run/rrd_temp.pid`

This is working on the core temperature sysctl value. For your OW application, you would only need to change "dev.cpu.0.temperature" to "dev.ow_temp.0.temperature".
 
That worked very well. I added the time bits to my basic c program.(I will do the full code next)
Code:
root@rpi2B:~ # cat /tmp/rrd_temp.log
2017-12-10 01:55:54   72.61 degF
2017-12-10 01:55:56   72.50 degF
2017-12-10 01:55:57   72.50 degF
2017-12-10 02:00:00   71.94 degF
2017-12-10 02:10:00   71.82 degF
2017-12-10 02:20:00   71.82 degF
2017-12-10 02:30:00   71.94 degF
2017-12-10 02:40:00   72.05 degF
2017-12-10 02:50:00   72.05 degF
2017-12-10 03:00:00   71.71 degF
2017-12-10 03:10:00   71.71 degF
2017-12-10 03:20:00   71.71 degF
2017-12-10 03:30:00   71.82 degF
2017-12-10 03:40:00   71.94 degF
2017-12-10 03:50:00   71.94 degF
2017-12-10 04:00:00   71.60 degF
2017-12-10 04:10:00   71.49 degF
2017-12-10 04:20:00   71.49 degF
2017-12-10 04:30:00   70.81 degF
2017-12-10 04:40:00   70.70 degF
2017-12-10 04:50:00   70.36 degF
2017-12-10 05:00:00   70.47 degF
2017-12-10 05:10:00   70.14 degF
2017-12-10 05:20:00   70.25 degF
2017-12-10 05:30:00   70.47 degF
2017-12-10 05:40:00   70.36 degF
2017-12-10 05:50:00   70.03 degF
2017-12-10 06:00:00   70.03 degF

In the end project rrd_update will not need the date. So this was pure learning on my part.
Thanks so much for taking the time to help me. It is really useful to see data in action.

After this I need to investigate rrdtool API and rrd_update function which is nice to have an 'C' API although not well documented.
http://permalink.gmane.org/gmane.comp.db.rrdtool.devel/894
 
...
In the end project rrd_update will not need the date. So this was pure learning on my part.
Thanks so much for taking the time to help me. It is really useful to see data in action.

Some sort of a time/date value you need to pass for the x-axis of the diagram, don't you?

...
After this I need to investigate rrdtool API and rrd_update function which is nice to have an 'C' API although not well documented.
http://permalink.gmane.org/gmane.comp.db.rrdtool.devel/894

I had a look at the link about the rrdtool C API. You can view at it as just another way of passing command line arguments, only instead of using the rrdupdate tool, by using the rrd_update() function -- the arguments are exactly the same, though. For this reason, I would start by setting up the system using only the rrd command line tools, and establishing the update mechanism on the command line with the rrdupdate tool as well -- simply by adding a few dummy values to the database. Once I got this working, I would copy/paste the code snippet from said link (that one below "Likewise for an update ... " into the for (;;) loop of the daemon as a replacement for the fprintf()/fflush() combo, and that should get me almost done.
 
I would start by setting up the system using only the rrd command line tools, and establishing the update mechanism on the command line with the rrdupdate tool as well -- simply by adding a few dummy values to the database. Once I got this working, I would copy/paste the code snippet from said link
Thanks this is exactly what I did.
I am having problems with the last three line's of code.

Code:
#include <stdio.h>
#include <rrd.h>           

char *updateparams[] = {
        "rrdupdate",
        "/ow_temp/ow_temp.rrd",
        "N:72",
        NULL
};

        optind = opterr = 0;
        rrd_clear_error();
        rrd_update(3, updateparams);

Here is the error:
Code:
root@rpi2B:/c # cc -L/usr/local/lib -lrrd -o rrd.app rrd.c
rrd.c:11:2: warning: type specifier missing, defaults to 'int' [-Wimplicit-int]
        optind = opterr = 0;
        ^
rrd.c:11:18: error: initializer element is not a compile-time constant
        optind = opterr = 0;
                 ~~~~~~~^~~
rrd.c:12:2: warning: type specifier missing, defaults to 'int' [-Wimplicit-int]
        rrd_clear_error();
        ^
rrd.c:12:2: error: conflicting types for 'rrd_clear_error'
/usr/include/rrd.h:358:15: note: previous declaration is here
    void      rrd_clear_error(
              ^
rrd.c:13:13: error: expected parameter declarator
        rrd_update(3, updateparams);
                   ^
rrd.c:13:13: error: expected ')'
rrd.c:13:12: note: to match this '('
        rrd_update(3, updateparams);
                  ^
rrd.c:13:2: warning: type specifier missing, defaults to 'int' [-Wimplicit-int]
        rrd_update(3, updateparams);
        ^
3 warnings and 4 errors generated.

Any help is appreciated. I tried adding int and char prefix.
With this section residing outside of the curly brackets{} I am not sure how to fix this.
 
Looking a /usr/include/rrd.h I see this:
Code:
/* NOTE: rrd_update_r and rrd_update_v_r are only thread-safe if no at-style
   time specifications get used!!! */

    int       rrd_update_r(
    const char *filename,
    const char *_template,
    int argc,
    const char **argv);
    int       rrd_update_v_r(
    const char *filename,
    const char *_template,
    int argc,
    const char **argv,
    rrd_info_t * pcdp_summary);

/* extra flags */
#define RRD_SKIP_PAST_UPDATES 0x01

    int       rrd_updatex_r(
So it looks like the code changed a little for thread safe and I will try rrd_update_r() instead.
I tried adding that additional #include and a whole lot more with no change.

Thanks everyone for the suggestions.
 
Threre is a section for rrd_update in rrd.h now I see:
Code:
    int       rrd_update(
    int,
    char **);
    rrd_info_t *rrd_update_v(
    int,
    char **);
 
Down to two errors but I commented out optind = opterr = 0;
Code:
ee /rrd.c
#include <stdio.h>
#include <string.h>
#include <syslog.h>          
#include <unistd.h>
#include <sys/stat.h>
#include <rrd.h>

char *updateparams[] = {
        "rrdupdate",
        "/ow_temp/ow_temp.rrd",
        "N:72",
        NULL
};

        //optind = opterr = 0;
void    rrd_clear_error();
int     rrd_update(3, updateparams);

"rrd.c" 23 lines, 407 characters
root@rpi2B:/c # cc -o rrd.app rrd.c
rrd.c:23:16: error: expected parameter declarator
int     rrd_update(3, updateparams);
                   ^
rrd.c:23:16: error: expected ')'
rrd.c:23:15: note: to match this '('
int     rrd_update(3, updateparams);
                  ^
2 errors generated.
 
Hm.
Line "i nt rrd_update(3, updateparams);" doesn't throw a redefinition warning. Is it declared/defined?
Maybe you wanted something like " int ret = rrd_update_r( 3 /* argc */, updateparams) " ?
 
OK Tried that:
Code:
#include <stdio.h>
#include <rrd.h>
#include <unistd.h>

char *updateparams[] = {
        "rrdupdate",
        "ow_temp.rrd",
        "N:72",
        NULL
};
void    rrd_clear_error();
int ret = rrd_update( 3 /* argc */, updateparams);
With one error:
Code:
root@rpi2B:/c # cc -o rrd.app rrd.c -lrrd
rrd.c:12:11: error: initializer element is not a compile-time constant
int ret = rrd_update( 3 /* argc */, updateparams);
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Thanks for you help.
 
Sorry, it was late yesterday, and I didn't realize, that the whole main() body in your C program file is missing. The following should work:
Code:
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <sys/sysctl.h>

#include <rrd.h>

size_t size = sizeof(int);
int    buf;
double tval;

char   timetemp[64];
char  *updateparams[] = {"rrdupdate", "ow_temp.rrd", timetemp, NULL};

int main(int argc, const char *argv[])
{
   sysctlbyname("dev.ow_temp.0.temperature", &buf, &size, NULL, 0);
   tval = (buf - 273150)*1.8e-3 + 32.0;
   snprintf(timetemp, 64, "%zd:%.4f", time(NULL), tval);

   optind = opterr = 0;
   rrd_clear_error();
   int rc = rrd_update(3, updateparams);
   if (rc != 0)
   {
      printf("RRD Update failed with result code %d\n", rc);
      return rc;
   }
 
   return 0;
}
 
Back
Top