yyrkoon

More C versus C++

54 posts in this topic

So this is not meant as a bashing of C, or C++. Just some observations I've made in the last couple of days while working with both C, and C++. On a Beaglebone, while toying around with reading from a DS18B20 1-wire sensor.

 

Also do keep in mind I'm not exactly an expert with either language, but I would say that I am more proficient in C, than C++.

 

First off. I often have to search the web for perhaps some common things related to any language. Not because I do not know how to do something. But because I often switch between languages for various things, and perhaps I do not remember specific details. Other times, maybe I'm not 100% sure what I need to use for a specific situation. Anyway . . .

 

With a DS18B20 1-wire sensor. In Linux, one has to setup a pin for 1-wire communications. It's fairly straight forward once you figure out how to create a proper device tree overlay for a Beaglebone. Once that is completed, and your kernel modules are loaded. You get a directory listing in /sys/bus/w1/devices/. This sub-directory is based off the sensors serial number, and starts off with "28-", followed by a 12 byte value representing this serial number. Example:

william@beaglebone:~/dev$ ls /sys/bus/w1/devices/
28-00000XXXXXXX  w1_bus_master1

Once I had this figured out, while I did not know exactly how I was going to deal with this in C, I knew it would be easy. Now since lately I had been reading up on C++, I figured I'd give it a go with C++. So I did a lot of reading on how to "properly" in C++ read from a file. Okay, no problem there, everything seemed equally as easy in C, just different, and perhaps more readable. As I really do like using classes when working with things of this nature. Then I brushed up on using strings, and various other C++-isms. Again, no problems, until I ran into a hitch. Traversing directories, and building paths from wildcards . . .

 

So this is what I've found out so far from reading many posts on stackoverflow, and other similar C++ forum sites. C++ has no way to work with directories in this manner. Other than falling back on C, and the struct dirent type. Now, I'm thinking to myself "how in the hell is this possible . . .". All while searching the web, more, and more, because I can not believe this is true. Right ? Wrong! So guys, what am I missing ?

 

Passed that, I'm following some C code I found on the web, to work with the sensors sysfs file entry to get data form the device. I then decided to attempt to port the code from C to C++. When I ran into another snag. When using open() on an ifstream object, it expects a const char * value for the path . . . So again, I'm thinking to myself  "wtf ?! I'm using a language where string is preferred over char arrays . . ." A language that Kate Gregory(So called C++ expert) proclaims to "stop using C" to teach C++ . . .

 

In addition to the above, in order to format double / floating point value to a specific precision . . .

std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield);
std::cout.precision(2);

So much for %.02f eh ?

 

Anyway, I was able to port most of the code over to C++, at the cost of an additional 14 lines of code. Which the original C code was only 57 lines. At which point I gave up. At least for the time being. Perhaps I need to read more, and get a better understanding of the C++ language. But this bit:

dir = opendir(path);
        if(dir != NULL){
                while((dirent = readdir(dir)))
                        // 1-wire devices are links beginning with 28-
                        if(dirent->d_type == DT_LNK && strstr(dirent->d_name, "28-") != NULL) {
                                strcpy(dev, dirent->d_name);
                                cout << endl << "Device: " << dev << endl;
                        }
                (void)closedir(dir);
        }
        else{
                cerr << "Couldn't open the w1 devices directory" << endl;
                return 1;
        }
        
        sprintf(devPath, "%s/%s/w1_slave", path, dev);
        time_t start_time = time(0);

Mostly remains straight C. Because I have not found a C++ equivalent. Which also brings me to time_t time(). . .

 

I do not know, perhaps I'm being pedantic. But when I use a language, I expect to use that language, and not have to rely on another . . . I am however starting to see why Linus Torvalds has a really bad attitude when it comes to C++.

 

 

 

[EDIT]

 

Do keep in mind when I say "C++ cant x.y.z . . .", I mean through the STL. I know there are libraries such as boost, and Qt, etc. But I really do not wish to deal with all that . . . for several reasons.

Share this post


Link to post
Share on other sites

Kate Gregory's point wasn't that you should stop using C. The talk was about better approaches to teach people C++ that don't know C and may even be new to programming. For that audience, starting with higher level concepts rather than pointers and char arrays makes a lot of sense.

 

Getting a const char* out of a string should be straight forward. In the days of classes (when I was doing large scale C++ projects), the string class usually had an operator that transparently took care of the conversion. Not sure if STL has the same (I think so) or whether you explicitly have to use c_str() method.

 

That being said, simply stick to the C++ constructs that make your live easier and don't feel bad about mixing them with plain C. I like to use classes to build better abstractions, or the template library for solid container constructs like lists. No benefit in being a purist in any direction.

yyrkoon likes this

Share this post


Link to post
Share on other sites

Kate Gregory's point wasn't that you should stop using C. The talk was about better approaches to teach people C++ that don't know C and may even be new to programming. For that audience, starting with higher level concepts rather than pointers and char arrays makes a lot of sense.

 

Getting a const char* out of a string should be straight forward. In the days of classes (when I was doing large scale C++ projects), the string class usually had an operator that transparently took care of the conversion. Not sure if STL has the same (I think so) or whether you explicitly have to use c_str() method.

 

That being said, simply stick to the C++ constructs that make your live easier and don't feel bad about mixing them with plain C. I like to use classes to build better abstractions, or the template library for solid container constructs like lists. No benefit in being a purist in any direction.

 

Not to argue, but I took away, from Kate's video on youtube, that instructors should not teach C, when teaching C++. I get that, and why it's good. But only if you're writing a "toy" program. Where you build your own string literals, and output to stdout. However, in the real world, where we need to create useful programs, a person using C++ as a primary language will need to know C.

 

So, I've watched all her video's from Pluralsite, and they're pretty good. She has a few for C++ fundamentals, one for STL, one for the C++ guidelines, and one Advanced C++ video which I haven't watched in full yet. But she doesn't cover anything "real world useful". Can I blame her ? Not really, but perhaps we should rethink the idea of teaching C++ without covering C at some point.

 

By the way, I did research, and use string.c_str(), and got an error. Something about a type mismatch I think. But the error message did not make any sense to me. Which is another "ding" in the "contest" that is C versus C++.

 

Eventually though, I hope to get all this whipped. Because despite me HATING C++ on the Windows platform( MS' bastardization of C++ anyhow ). I really like many aspects of the language.

Share this post


Link to post
Share on other sites

The c++ way on a real os like linux would be to the boost library. It is all about iterators.

 

http://www.boost.org/doc/libs/1_59_0/libs/filesystem/example/simple_ls.cpp

 

-rick

 

I was rather hoping to avoid external libraries, but I guess in this case I have no choice eh ? Not that I think boost is bad . . .But I guess there are a lot of external libraries for C, under Linux. I don't know, would you consider all the libc stuff "external" ?

Share this post


Link to post
Share on other sites

I was rather hoping to avoid external libraries, but I guess in this case I have no choice eh ? Not that I think boost is bad . . .But I guess there are a lot of external libraries for C, under Linux. I don't know, would you consider all the libc stuff "external" ?

If the code you wrote above does what you want why do you want to be "c++ complete" ?

 

The big advantage of boost is that it is cross platform and mostly made up of c++ templates except for the operating system stuff (which filesystem code is).  If you aren't planning on running this code on any other OS than linux then maybe it isn't worth your time.   Only you can decide what is time worthy.

 

-rick

yyrkoon likes this

Share this post


Link to post
Share on other sites

@@chicken

 

Yeah, ok, I was "doing it wrong". This is what I was trying to convert:

sprintf(devPath, "%s/%s/w1_slave", path, dev);

And what I wound up with that works is this:

string dPath = string(path) + string("/") + string(dev) + string("/w1_slave");

So I was just tossing in the character arrays without converting to string prior. In retrospect, this makes sense to me now. Now string.c_str() works fine:

ifstream devFile(dPath.c_str());

And the output:

william@beaglebone:~/dev$ g++ -Wall -o read-temp read-temp.cpp
william@beaglebone:~/dev$ ./read-temp

Device: 28-00000647ddf6
18.12C
18.12C
18.12C
18.19C
^C

 

Share this post


Link to post
Share on other sites

If the code you wrote above does what you want why do you want to be "c++ complete" ?

 

The big advantage of boost is that it is cross platform and mostly made up of c++ templates except for the operating system stuff (which filesystem code is).  If you aren't planning on running this code on any other OS than linux then maybe it isn't worth your time.   Only you can decide what is time worthy.

 

-rick

At this point, I'm trying to get rid of all char arrays, char * const char *, etc. In my mind, these are C-isms, and not C++ related per se. Sure, we can use char arrays, and printf(), in C++. But if we do, what's the point of using C++ ? We have string, and cout.

 

No, I'm not trying to be a C++ purist really. I just want to use various things in C++ that I think makes the language worthwhile using. the string type( or class if you prefer) is one of those things I think makes C++ worth using. Now, I guess I just need to learn how to use string ;)

Share this post


Link to post
Share on other sites

I think this is about as close as I'll get for this simple application. There are perhaps a couple things I could refactor into C++, but I'm just not sure it's worth it. Boost, has a timer class I was reading about yesterday, but the very simple "timer" I setup in 3 lines of code total, does the job just fine. using time.h

#include <iostream>
#include <fstream>
#include <string>

#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

using namespace std;

int main(int argc, char **argv) {

    DIR *dir;
        struct dirent *dirent;
        char path[] = "/sys/bus/w1/devices";

        string dev;

        dir = opendir(path);
        if(dir != NULL){
                while((dirent = readdir(dir)))
                        // 1-wire devices are links beginning with 28-
                        if(dirent->d_type == DT_LNK && strstr(dirent->d_name, "28-") != NULL) {
                                dev = string(dirent->d_name);
                                cout << endl << "Device: " << dev << endl;
                        }
                (void)closedir(dir);
        }
        else{
                cerr << "Couldn't open the w1 devices directory" << endl;
                return 1;
        }
        
        string dPath = string(path) + string("/") + dev + string("/w1_slave");

        time_t start_time = time(0);

        std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield);
    std::cout.precision(2);

    while(1){

        if((time(0) - start_time) >= 1){    // Get rid of magic number "1"
            start_time = time(0);
            
            ifstream devFile(dPath.c_str());
            if (devFile.is_open()) {
                string line1, line2;

                getline(devFile, line1);
                getline(devFile, line2);

                size_t pos = line2.find("t=");
                string s = line2.substr(pos + 2);

                cout << atof(s.c_str()) / 1000 << "C" << endl;

                devFile.close();
            }    
        }
        usleep(200000);
    }
    return 0;
}

I'm sure you guys will find something wrong with that code ;) Feel free to speak up.

Share this post


Link to post
Share on other sites

OK, so after eating dinner, and relaxing a bit I came back with to my "problem" here with some ideas . . .

 

First up:

if (dirent->d_type == DT_LNK && strstr(dirent->d_name, "28-") != NULL) {
    strcpy(dev, dirent->d_name);
    printf("\nDevice: %s\n", dev);
}

Got replaced with:

if (string(dirent->d_name).find("28-") != string::npos) {
    dev = string(dirent->d_name);
    cout << endl << "Device: " << dev << endl;
}

To "port" this part to all C++. Additionally, dirent->d_type == DT_LNK was an unnecessary conditional test. As we're already in the /sys/ sysfs path, so whatever is here is going to be a link. Then, is not, well we already know our directory will be . . . assuming it exists.

 

secondly:

string path = "/sys/bus/w1/devices";
string dev;

dir = opendir(path.c_str());

. . .

string dPath = path + "/" + dev + "/w1_slave";

I got rid of all C style strings. This allowed me to remove the #include for string.h, which made me feel better. Anyway, maybe it's just me, but I feel that one of C++ strengths is the ability to work with string, versus C style strings. Yes, technically both can be used, but why ?

 

Lastly, I made some changes to how the string numerical value for temperature output is parsed into a floating point value:

if (devFile.is_open()) {
    string line1, line2;

    getline(devFile, line1);
    getline(devFile, line2);

    float temp = 0;
    size_t pos = line2.find("t=");

    string s = line2.substr(pos + 2);
    istringstream iss(s);

    iss >> temp;
    cout << temp / 1000 << "C" << endl;

    devFile.close();
}

Here, I'm letting istringstream do the conversion for me. I honestly have no idea if there are performance issues involved here. But if there are, I really can't tell. This has the effect of making me feel "all warm and fuzzy" in that I feel that this is how such a conversion should be done in C++. Maybe I'm wrong ? Perhaps it'll take a while for me to know one way or another. . .  Obviously there are a few things left that I can't help feel are "C-isms". But since I'm including C style headers, and using some C API functions . . . this can't be helped, I'm ok with that.

 

Full code listing:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

#include <dirent.h>
#include <time.h>
#include <unistd.h>

using namespace std;

int main(int argc, char **argv) {

        DIR *dir;
        struct dirent *dirent;
        
        string path = "/sys/bus/w1/devices";
        string dev;

        dir = opendir(path.c_str());
        if(dir != NULL){
                while((dirent = readdir(dir)))
                        // 1-wire devices are links beginning with 28-
                        if(string(dirent->d_name).find("28-") != string::npos) {
                                dev = string(dirent->d_name);
                                cout << endl << "Device: " << dev << endl;
                        }
                closedir(dir);
        }
        else{
                cerr << "Couldn't open the w1 devices directory" << endl;
                return 1;
        }
        
        string dPath = path + "/" + dev + "/w1_slave";

        time_t start_time = time(0);

        std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield);
        std::cout.precision(2);

    while(1){

        if((time(0) - start_time) >= 1){    // Get rid of magic number "1"
            start_time = time(0);
            
            ifstream devFile(dPath.c_str());
            if (devFile.is_open()) {
                string line1, line2;

                getline(devFile, line1);
                getline(devFile, line2);

                float temp = 0;
                size_t pos = line2.find("t=");
                
                string s = line2.substr(pos + 2);
                istringstream iss(s);

                iss >> temp;
                cout << temp / 1000 << "C" << endl;

                devFile.close();
            }    
        }
        usleep(200000);
    }
    return 0;
}

Share this post


Link to post
Share on other sites

For informational purposes. Here is the code I started off with:

#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

int main (void) {
        DIR *dir;
        struct dirent *dirent;
        char dev[16];      // Dev ID
        char devPath[128]; // Path to device
        char buf[256];     // Data from device
        char tmpData[6];   // Temp C * 1000 reported by device
        char path[] = "/sys/bus/w1/devices";
        ssize_t numRead;

        dir = opendir (path);
        if (dir != NULL)
        {
                while ((dirent = readdir (dir)))
                        // 1-wire devices are links beginning with 28-
                        if (dirent->d_type == DT_LNK && strstr(dirent->d_name, "28-") != NULL) {
                                strcpy(dev, dirent->d_name);
                                printf("\nDevice: %s\n", dev);
                        }
                (void) closedir (dir);
        }
        else
        {
                perror ("Couldn't open the w1 devices directory");
                return 1;
        }

        // Assemble path to OneWire device
        sprintf(devPath, "%s/%s/w1_slave", path, dev);
// Read temp continuously
// Opening the device's file triggers new reading
        while (1) {
                int fd = open(devPath, O_RDONLY);
                if (fd == -1)
                {
                        perror ("Couldn't open the w1 device.");
                        return 1;
                }
                while ((numRead = read(fd, buf, 256)) > 0)
                {
                        strncpy(tmpData, strstr(buf, "t=") + 2, 5);
                        float tempC = strtof(tmpData, NULL);
                        printf("Device: %s  - ", dev);
                        printf("Temp: %.3f C  ", tempC / 1000);
                        printf("%.3f F\n\n", (tempC / 1000) * 9 / 5 + 32);
                }
                close(fd);
        }
        return 0;
}

In some ways "more beautiful"( I honestly in most cases prefer C style syntax ), but also a mess, and perhaps slightly less readable ? *shrug*

Share this post


Link to post
Share on other sites

You are linux. Maybe you should just bash it:

#!/bin/bash

SENSOR_FILES=$(echo /sys/bus/w1/devices/28-*)

while true; do
  for sensor in ${SENSOR_FILES}; do
	VALUE=$(sed -e 's/t=//' < $sensor);
    echo "scale=2; ${VALUE}/1000.0" | bc;
  done;
  sleep .2
done;

-rick

yyrkoon likes this

Share this post


Link to post
Share on other sites

You are linux. Maybe you should just bash it:

#!/bin/bash

SENSOR_FILES=$(echo /sys/bus/w1/devices/28-*)

while true; do
  for sensor in ${SENSOR_FILES}; do
	VALUE=$(sed -e 's/t=//' < $sensor);
    echo "scale=2; ${VALUE}/1000.0" | bc;
  done;
  sleep .2
done;

-rick

 

Naw. A shell script may be more concise, and quicker to write, but it'll eat up more CPU in the process. Right now, on the platform I'm experimenting with this code for. There are several processes that may be vying for CPU time. So the more performant the code I run, the better off the system in whole will be. As I already have a C program that checks the state of the ethernet device every 500ms. While there are many other processes that deal with GPIO's PWM's, and an ADC, that all publish MQTT data.

 

Not to mention you're not exactly doing in that script, what I'm doing in my code. I "sleep" 200ms as a throttling mechanism to give time back to the CPU while I'm waiting for my "timer" code to fire. Eventually, I'll refactor out the hard coded 1 second timer, and make this value set-able via a cmdline arg. Which actually, there is still technically one C style string array in the program. If we count argv. Which . . .  using getopt in C++ will easily turn into a bloody mess I think.

 

Besides, this allows me to obfuscate my code somewhat.

Share this post


Link to post
Share on other sites

Naw. A shell script may be more concise, and quicker to write, but it'll eat up more CPU in the process.

If you really want to see the resource being used, try the time comand:

$ time /tmp/doit.sh
19.34
18.56
19.34
18.56
19.34
18.56
19.34
18.56
^C
real 0m1.776s
user 0m0.008s
sys 0m0.000s

run that on your c code and a script and see what the difference actually is. I think will be surprised how little is going on.

 

-rick

 

BTW: sleep .2 is 200 ms sleep

Share this post


Link to post
Share on other sites

@@Rickta59

 

Also I suspected that script wouldn't work, so I tested it. After modifying the file path. You were pointing to a directory, not the file we need to read from . . .

william@beaglebone:~/dev$ ./btemp.sh
(standard_in) 1: syntax error
(standard_in) 1: illegal character: :
(standard_in) 1: illegal character: Y
(standard_in) 1: syntax error
(standard_in) 1: illegal character: S
(standard_in) 2: syntax error
^C
william@beaglebone:~/dev$ cat btemp.sh
#!/bin/bash

SENSOR_FILES=$(echo /sys/bus/w1/devices/28-*/w1_slave)

while true; do
  for sensor in ${SENSOR_FILES}; do
        VALUE=$(sed -e 's/t=//' < $sensor);
    echo "scale=2; ${VALUE}/1000.0" | bc;
  done;
  sleep .2
done;

So, a read form this file actually returns two lines. Like this:

william@beaglebone:~/dev$ cat /sys/bus/w1/devices/28-*/w1_slave
20 01 4b 46 7f ff 10 10 fc : crc=fc YES
20 01 4b 46 7f ff 10 10 fc t=18000

Where the bytes, are the actual bitfields the sensor sends back form a read request. Why it's sent twice, I'm not sure. except perhaps to verify the crc value ?

 

 

 

Share this post


Link to post
Share on other sites

BTW: sleep .2 is 200 ms sleep

 

I know . . . or two tenths of a second ;)

william@beaglebone:~/dev$ time ./read-temp

Device: 28-00000647ddf6
18.00C
18.06C
18.06C
18.06C
18.06C^C

real    0m5.394s
user    0m0.000s
sys     0m0.072s

So how does .000 usertime compare to .008 ? ;)

 

@@Rickta59

 

Come back lol . . .I spent nearly as much time in kernel that you spent in user space. Not exactly sure if that's a bad thing or not.

 

[EDIT] actually nearly 10 times as much. But, and I'm assuming this has to do with my program runs a looser loop, thus ran longer, and time() + opendir() etc are sys calls which do land in kernel mode.

 

Most of that is initial application setup that runs once, but I'm also opening and closing the file descriptor once every second. Which I'm not sure what you're doing there with echo . . . either way, it doesn't work on the beaglebone( Debian Jessie 8.6 ), and cat in place of echo doesn't work either. The script runs too fast to read from the sensor properly. So one thing to know about the DS18B20 is that it's data refresh rate can be anywhere between 100, and 750 milliseconds. Depending on how it's configured. I have not got aroudn ot figurering out how to configure the sensor through Linux, but I'm not sure I will either. Chances are pretty good ill only be reading the the sensor once every minute, maybe even more.

 

So here is part of what strace has to say about your script:

read(3, "20 01 4b 46 7f ff 10 10 fc : crc"..., 128) = 73
read(3, "", 128)                        = 0
close(3)  

What this tells me is that you probably need to use read_line() as it looks like it's only catching the first half of the output from the sensor. By the way, it also almost looks as the crc value is not correct either. But who knows what strace is doing there . . .

Share this post


Link to post
Share on other sites

I'm a little embarrassed to admit, but, I had no idea the Linux 'sleep' command could take a decimal point..... always thought it had to use integers.

 

Turns out it takes suffixes for 's' (seconds, the default), 'm' (minutes), 'h' for hours and 'd' for days too!

DESCRIPTION
       Pause  for  NUMBER  seconds.  SUFFIX may be 's' for seconds (the default), 'm' for minutes, 'h' for hours or 'd' for days.  Unlike most implementa?
       tions that require NUMBER be an integer, here NUMBER may be an arbitrary floating point number.  Given two or more arguments, pause for the  amount
       of time specified by the sum of their values.

Share this post


Link to post
Share on other sites

Ok so when I wrote the first script I didn't really know what the w1_slave returned, it was more an example of what you might do. I went and dug up what I think the w1_slave returns.  Here is the format I think it returns, but I really don't know you will have to adjust for it

 

Assumming it returns something like this:

$ cat /tmp/bus/w1/devices/28-100000001/w1_slave
4b 01 4b 46 7f ff 05 10 e1 : crc=e1 YES
4b 01 4b 46 7f ff 05 10 e1 t=19065
You could use this script:

#!/bin/bash

# read the w1-temp values
# put each line1 and 2 into variables
# parse and see if it is ready
# if so read the raw value and round it to 2 decimals
read_sensor()
{
    SENSOR_FILE=$1
    local __celsius_raw=$2
    local __celsius_dec=$3
    local __rc=$4

    local indx=0;
    while IFS='' read -r temp; do
        case $indx in
        0) line1=$temp;;
        1) line2=$temp;;
        esac
        indx=$((indx+1))
    done < $SENSOR_FILE

    ready=${line1/YES/,ready}

    if [ "${ready#*,}" == "ready" ] 
    then
        local celsius_raw=${line2#*t=}
        local celsius_dec=$(echo "scale=2; ((${celsius_raw}+5)/1000.0)" | bc)
        eval $__rc="1"
        eval $__celsius_raw="'$celsius_raw'"
        eval $__celsius_dec="'$celsius_dec'"
    else
        eval $__rc="0"
    fi
}

# loop through all the sensors
SENSOR_FILES=$(echo /sys/bus/w1/devices/28-*/w1_slave)

while true; do
    for sensor_file in ${SENSOR_FILES}; do
    read_sensor $sensor_file c d rc
    if [ "$rc" == "1" ]; then
        echo "Raw=${c}"
        echo "Celsius=${d}"
    else
        >&2 echo -n "."
    fi
    done
  sleep 0.2
done;
This one uses only one external command "bc" to convert the raw value to a rounded one

 

$ time ./w1_poll.sh

real 0m5.560s

user 0m0.012s

sys 0m0.008s

 

 

-rick

 

Also here: https://gist.github.com/RickKimball/926a6e638d6fea994384af4d8487078f

Share this post


Link to post
Share on other sites

 

I'm a little embarrassed to admit, but, I had no idea the Linux 'sleep' command could take a decimal point..... always thought it had to use integers.

 

Turns out it takes suffixes for 's' (seconds, the default), 'm' (minutes), 'h' for hours and 'd' for days too!

DESCRIPTION
       Pause  for  NUMBER  seconds.  SUFFIX may be 's' for seconds (the default), 'm' for minutes, 'h' for hours or 'd' for days.  Unlike most implementa?
       tions that require NUMBER be an integer, here NUMBER may be an arbitrary floating point number.  Given two or more arguments, pause for the  amount
       of time specified by the sum of their values.

 

Yeah, been that way for a while now, with .1 being the lowest possible value. I still use usleep though for some reason I do not even know why. Familiarity I guess.

Share this post


Link to post
Share on other sites

Ok so when I wrote the first script I didn't really know what the w1_slave returned, it was more an example of what you might do. I went and dug up what I think the w1_slave returns.  Here is the format I think it returns, but I really don't know you will have to adjust for it

 

Assumming it returns something like this:

$ cat /tmp/bus/w1/devices/28-100000001/w1_slave
4b 01 4b 46 7f ff 05 10 e1 : crc=e1 YES
4b 01 4b 46 7f ff 05 10 e1 t=19065
You could use this script:

#!/bin/bash

# read the w1-temp values
# put each line1 and 2 into variables
# parse and see if it is ready
# if so read the raw value and round it to 2 decimals
read_sensor()
{
    SENSOR_FILE=$1
    local __celsius_raw=$2
    local __celsius_dec=$3
    local __rc=$4

    local indx=0;
    while IFS='' read -r temp; do
        case $indx in
        0) line1=$temp;;
        1) line2=$temp;;
        esac
        indx=$((indx+1))
    done < $SENSOR_FILE

    ready=${line1/YES/,ready}

    if [ "${ready#*,}" == "ready" ] 
    then
        local celsius_raw=${line2#*t=}
        local celsius_dec=$(echo "scale=2; ((${celsius_raw}+5)/1000.0)" | bc)
        eval $__rc="1"
        eval $__celsius_raw="'$celsius_raw'"
        eval $__celsius_dec="'$celsius_dec'"
    else
        eval $__rc="0"
    fi
}

# loop through all the sensors
SENSOR_FILES=$(echo /sys/bus/w1/devices/28-*/w1_slave)

while true; do
    for sensor_file in ${SENSOR_FILES}; do
    read_sensor $sensor_file c d rc
    if [ "$rc" == "1" ]; then
        echo "Raw=${c}"
        echo "Celsius=${d}"
    else
        >&2 echo -n "."
    fi
    done
  sleep 0.2
done;
This one uses only one external command "bc" to convert the raw value to a rounded one

 

$ time ./w1_poll.sh

real 0m5.560s

user 0m0.012s

sys 0m0.008s

 

 

-rick

 

Also here: https://gist.github.com/RickKimball/926a6e638d6fea994384af4d8487078f

 

Thanks Rick I appreciate the effort. You realize there are about 5million bash scripts for 1-wire and the rPI ? Some really nice ones too.

Share this post


Link to post
Share on other sites

Thanks Rick I appreciate the effort. You realize there are about 5million bash scripts for 1-wire and the rPI ? Some really nice ones too.

 

 I didn't actually look no. If there are, why are you writing another solution?

Share this post


Link to post
Share on other sites

I

 

 I didn't actually look no. If there are, why are you writing another solution?

 I already told you why last night. That, and what you've seen is not all I'll be doing.

Share this post


Link to post
Share on other sites

# best solution

$ while true; do for sensor_file in /tmp/bus/w1/devices/28-*/w1_slave; do echo "scale=2; $(cat ${sensor_file} | grep  -E -o ".{0,0}t=.{0,5}" | cut -c 3- )/1000.0" | bc; done; sleep .5; done

yyrkoon likes this

Share this post


Link to post
Share on other sites

# best solution

$ while true; do for sensor_file in /tmp/bus/w1/devices/28-*/w1_slave; do echo "scale=2; $(cat ${sensor_file} | grep  -E -o ".{0,0}t=.{0,5}" | cut -c 3- )/1000.0" | bc; done; sleep .5; done

Yeah there are a bunch of one liners out there.

 

But as I'm not only going to be reading the file, and displaying the temp to screen. That was just me experimenting, and then "obsessing" to convert a C program to C++.

 

So first off the final application I'll be writing will be for a client, billing them 3-4 hours for the software. Then probably an additional 1-2 hours for a setup script to make sure everything needed is in place. Then if not, install all the needed goodies. While I'm not quite sure what the client wants *YET* my idea is to log the data out via MQTT. Then it becomes a question of how often do we log. Here, I'm thinking we probably want a command line option variable between say 1-60 seconds. 1 second to create a temperature profile of the board, if needed. Then 60 seconds normal checking / logging. Maybe even an additional parameter to only log when the temperature is within a certain range. e.g. board is close to or is overheating, so start logging. With perhaps yet another parameter to tell the board to shutdown when overheating. There is even a web front end, as well as an iPhone/Android app in the works.

 

At this point, writing a shell script probably is not ideal. Not to mention it'll get very messy quickly. Perhaps even slow.

 

So for a one off board type project, DiY style. *MAYBE* a shell script. But there are multiple boards, nation wide, logging data out to a server <somewhere else> in the US. Think 10's of boards at this point (30-40) deployed, with around 50-60 total made so far. Just for one type of board, there are actually 3-4 variations for different situations, with only the first variation being deployed in the wild _right_now_. The original idea though( not mine, the clients ) is actually really cool, and innovative.

Share this post


Link to post
Share on other sites

So coming back to this problem again, just now. I decided for my current purpose to use a shell script. Mostly because a shell script should be readable by most, that are familiar with UNIX like systems, and I'm not the only one maintaining this system.

 

@@Rickta59

bc is a problem, because it does not come default with the current debian image we're working with. Sure I could install bc using APT, but why waste the effort and space ? I did create a simple solution though based off a couple of stackoverflow posts I searched for . . .

root@beaglebone:~# temp=$(cat /sys/bus/w1/devices/28-*/w1_slave |awk ' /t=/ {print $10}')
root@beaglebone:~# echo ${temp:2}
16812

This is of course fixed type, but I do not necessarily need a floating point type do I ? I don't think so . . . But going back to our discussion from before. Running cat on a 1-wire device, there is a very noticeable 2-3 second pause at the beginning. This pause, as I recall does not happen when I ran the C++ code mentioned previously. With that said, for the purpose I'm using this for. It doesn't matter.

 

EDIT:

 

If floating point did matter though . . .

root@beaglebone:~# temp=$(cat /sys/bus/w1/devices/28-*/w1_slave |awk ' /t=/ {print $10}')
root@beaglebone:~# awk "BEGIN {print ${temp:2}/1000}"
16.937

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now