Multiple DS18B20 with Photon


#127

Sounds great @ScruffR, thanks for sharing this link!
It is certainly worth checking it against my requirements (see previous post).

I have just tried it first in the Web IDE and I got this error message:

Actually, I got this every time I compile a sketch using OneWire library in the Web IDE…

I tried it in ParticleDev with your library and with the OneWire library. It compiles and uploads OK.

Although I adapted the sensor pin to D3 and number of sensors to 6 for my current setup, I got only 2 values of which one seems wrong:

I don’t know what I am doing wrong, but never mind:
I believe this is a great tool to “discover” a number of sensors and publish their value, but it (probably) will be a big job again to adapt it to both my above requirements…

Good night…
:frowning:


#128

The only 2 disadvantages of this sketch I see:

  • You must run another sketch to identify the sensor addresses before being able to use it “hardwired”
  • No CRC checking is used, to filter out stray values

I get it, you need to know which sensors are which.

most libraries, while including searching method, should allow you to hardcode the unique addresses when reading.

the better libraries include CRC checking, because of the known issues (which you are well aware).

While @Ric did a great job organizing your code, I’d still go the extra step of creating an object oriented approach as to greatly simplify your code and organize your data. But that is another lesson!

I’ve watched this thread for a while, I’m rooting for you to get this perfected!!!


#129

Thanks @BulldogLowell, I see you are with me!

I guess that’s right, but I am so happy with @Ric’s solution that I would prefer to refine that one further with CRC checking rather than to try all over again starting with another library. Let me know if you don’t agree and which one gives us a better solution… :wink:

Concerning the extra “filtering” needed:
I remembered another exercise with @Ric, developing a “filtering method”: LINK
Probably that’s an even better way than my previous idea…

What do you think?
:sleeping:


#130

Do you mean with arrays?
More simplification would be great, but usability is more important for my applications. The requirements in my earlier post are vital!
:sleeping:


#131

sounds like the gauntlet has been thrown… I’m sure @Ric is up to the task.

I’d create a struct like this for the pairs of sensors:

struct Zone{
    byte highAddress[8]; // high sensor
    byte lowAddress[8];  // low sensor
    double highTemp;
    double lowTemp;
    double averageTemp;
    double energy;
};

void doSomethingWith(Zone* zone);

and an array of those objects like this:

Zone tempSensor[]{
    {{0x28,0xFF,0x0D,0x4C,0x05,0x16,0x03,0xC7}, {0x28,0xFF,0x25,0x1A,0x01,0x16,0x04,0xCD}}, 
    {{0x28,0xFF,0x89,0x19,0x01,0x16,0x04,0x57}, {0x28,0xFF,0x21,0x9F,0x61,0x15,0x03,0xF9}}, 
    {{0x28,0xFF,0x16,0x6B,0x00,0x16,0x03,0x08}, {0x28,0xFF,0x90,0xA2,0x00,0x16,0x04,0x76}}
};

you can assign names to them as you did in @Ric 's code:

Zone* Tank1 = &tempSensor[0];
Zone* Tank2 = &tempSensor[1];
Zone* Tank3 = &tempSensor[2];

Zone* myTanks[] = {Tank1, Tank2, Tank3};

then call a function on the objects:

for (auto& eachTank : myTanks)
{
    doSomethingWith(eachTank);
}

maybe a bit tricky to grasp, but there are (in this example) three tank objects each that retain two temperatures, their average and the energy… kind-of-thing.


#132

I might be in a couple of days, but I don’t want to try to write code on my phone ( I’m in Yosemite park right now).:wink:


#133

I guess you are not targeting a system version greater 0.5.2 in Web IDE.


#134

I was targeting v0.6.2
Should I aim lower?


#135

Sounds interesting, but indeed it’s over my head…

As far as I’m concerned I’m fine with @Ric 's final sketch (without library!) as it is easy to use and very reliable.

What would improve with the way you suggest here?


#136

Hmm, that’s surprising.
When I USE THIS EXAMPLE ds18b20-multidrop.ino in Web IDE as is I only get this error when targeting a version 0.5.2 or before.

You may be mistaking the installed version on the device (v0.6.2) in the bottom right corner in Web IDE as the target version.
The actual target version is set in the target drawer (third icon up on the left hand bar) and I guess when you open the device info (chevron >) next to your device you’ll see you are not targeting 0.6.2.


#137

Yes, that’s what I thought it was…
Indeed, it was targeting a lower version!
I set it to v0.6.2 and Hoopla it compiles!
Thanks for pointing that out @ScruffR

All my other Particle devices have v0.6.2 targeted… How did it change back on this device?

Q: Do we need to set this target version manually for each device or will it be set sometimes by Particle?


#138

@Ric : Don’t worry and enjoy your adventure @Yosemite!

I believe the QSORT filtering you described earlier (LINK) will really perfect your sketch (without library)

It’s probably all we need to filter out all stray values. Isn’t it?

This is how the recording for one boiler (6 sensors) looks currently:


#139

This is a per device setting and will not be adapted by Particle (deliberately - but glitches might occure).
The only “exception” to this is when you chose Default (x.y.z) (currently Default (0.6.2)) as target. This instructs Web IDE to keep targeting the most recent official release.

This is intended, since some projects (for particlular devices) might rely on some “glitches” of older versions and if Particle would mess with your settings, you’d not be too happy if a fine running project suddenly threw up unexpectedly.


#140

Very useful, thanks @ScruffR!


#141

@FiDel,

This uses @ScruffR 's library that includes CRC checking. I introduced the object-oriented approach I mentioned before.

Of course, i cannot test it but you would have to check that the top, mid and bot sensors were the correct addresses.

#include <DS18B20.h>

struct Zone{
    char sensorName[4];
    uint8_t hiSensor[8]; 
    uint8_t loSensor[8]; 
    double hiTemp;
    double loTemp;
    double avgTemp;
    double energy;
};

void readSensorSet(Zone* zone);
double getTemp(uint8_t addr[8]);

Zone tempSensor[]{
    {"Top", {0x28,0xFF,0x0D,0x4C,0x05,0x16,0x03,0xC7}, {0x28,0xFF,0x25,0x1A,0x01,0x16,0x04,0xCD}}, 
    {"Mid", {0x28,0xFF,0x89,0x19,0x01,0x16,0x04,0x57}, {0x28,0xFF,0x21,0x9F,0x61,0x15,0x03,0xF9}}, 
    {"Bot", {0x28,0xFF,0x16,0x6B,0x00,0x16,0x03,0x08}, {0x28,0xFF,0x90,0xA2,0x00,0x16,0x04,0x76}}
};

Zone* Top = &tempSensor[0];
Zone* Middle = &tempSensor[1];
Zone* Bottom = &tempSensor[2];

Zone* sensorSet[] = {Top, Middle, Bottom};

double Qtot;

const int pinOneWire = D2;
const int pinLED = D7;

DS18B20 ds18b20(pinOneWire);

void setup() 
{
  Serial.begin(9600);
  
  for (auto set : tempSensor)
  {
      Particle.variable(String(set.sensorName) + "H", set.hiTemp);
      Particle.variable(String(set.sensorName) + "L", set.loTemp);  // EDIT fixed typo
  }
  Particle.variable("ECO-Qtot", Qtot);
}

void loop() 
{
  double accumulatedEnergy = 0;
  for (auto& set : tempSensor)
  {
      set.hiTemp = getTemp(set.hiSensor);
      set.loTemp = getTemp(set.loSensor);
      set.avgTemp = (set.hiTemp + set.loTemp) / 2;
      set.energy = (set.avgTemp - set.loTemp) * 110 * 1.163 / 1000;
      accumulatedEnergy += set.energy;
  }
  Qtot = accumulatedEnergy;
  delay(5000);
}

double getTemp(uint8_t addr[8]) 
{
    static const int MAXRETRY = 3;
    double _temp;
    int i = 0;

    do {
         _temp = ds18b20.getTemperature(addr);
    } while (!ds18b20.crcCheck() && MAXRETRY > i++);

    if (i < MAXRETRY) 
    {
        //celsius = _temp;
        //fahrenheit = ds18b20.convertToFahrenheit(_temp);
        Serial.println(_temp);
    }
    else 
    {
        _temp = NAN;
        //celsius = fahrenheit = NAN;
        Serial.println("Invalid reading");
    }
    return _temp;
}

i’m not using:

Zone* Top = &tempSensor[0];
Zone* Middle = &tempSensor[1];
Zone* Bottom = &tempSensor[2];

Zone* sensorSet[] = {Top, Middle, Bottom};

#142

Thanks for this effort @BulldogLowell!

I will certainly come back later to this possible alternative when I have more time for experimenting AND when I’m close to the Photon used for this (I’m living one hour drive from there and cannot use serial output. That’s why I use Particle.variables instead)

First I prefer to further “polish” the sketch from @Ric, integrating another of his good ideas: QSORT “median filtering”. I believe that combination will do exactly what I need, even without CRC checks.

Also, I’d like to see the clear benefits of your proposed approach first.
For my home project it is a very essential application, as it will finally be running on 10+ Photons (and also 2 Electrons later)

When comparing the application size, it is not an advantage:

Your application (sketch + library)

  • Your sketch = 1968 bytes
  • DS18B20.cpp = 4251 bytes
  • DS18B20.h = 1129 bytes

Total = 7350 bytes => vs @Ric’s sketch Total = 2121 bytes

To be continued… :wink:


#143

I included the Particle Variables, just as you have.

adding filtering to this is somewhat trivial.

either fits fine on a Photon and neither are doing things that may affect stability in the long term.

i feel that it is:

  1. easier to read and understand what is happening… you are not moving pointers around an array which is a composite of several physical objects. this organizes the objects as they are “installed”
  2. does the CRC checking, which is basically necessary to avoid data blips (the “belt”)
  3. easier to make changes to the code, like adding the “suspenders” (software filtering)
  4. simpler code (well, if you happen to be the one writing it!)

:wink:


#144

This is a common misconception - that file size is important in defining what is “good” or not. Clarity, portability and sustainability (ability to manage or extend the code) is more important. I suspect the compiled file size is a) very similar and b) irrelevant since both will leave plenty of space for other code, if needed at all.


#145

Of course @peekay123, I can agree with what you state about “size does not matter”… :wink:

But in my 10 room sketches for the 10 Photons in my (home automation) project in progress, so many other libraries and scripts need to be ‘interwoven’ for various functions that I am constantly alerted to keep the footprint of each app as small (and simple) as possible…

Is that not a correct approach?


#146

hard to argue the contrary there.

So, I too and a bit sniped by this problem.

I thought about this a bit and figured that if I am adding CRC checks from @ScruffR 's library, then all we need to do is add the filtering.

If you are filtering, why arbitrarily de-select the highest or lowest values? If they are valid readings, then you could use a variation based on reasonable mathematics (e.g. standard deviation).

so I added a method (in this example up to 25) for temperatures to accumulate and added new readings to the value if and only if the current reading is inside a given multiple of standard deviations (I used 1.5 in this code, but you could use any number).

I thought about using qsort() but I’d have to learn how, so I decided to use C++ methods instead.

I whacked this out at lunch… maybe take it for a try while the forum gurus evaluate the code. :blush:

#include <algorithm>
#include <vector>

#include <DS18B20.h>

#define SAMPLE_SIZE 25
#define STANDARD_DEVIATION_FILTER 1.5  // we will toss any value that isnt within this number of standard deviations of the past BUFFER_SIZE readings

struct Zone{
    char sensorName[4];
    uint8_t hiSensor[8]; // high sensor
    uint8_t loSensor[8];  // low sensor
    double hiTemp;
    double loTemp;
    double avgTemp;
    double energy;
    std::vector<double> hiArray;
    std::vector<double> loArray;
    
    bool addHiTemp(double newVal)
    {
        if (hiArray.size() == 0)
        {
            hiArray.push_back(newVal);
            return true;
        }
        double sum = std::accumulate(hiArray.begin(), hiArray.end(), 0.0);
        double mean = sum / hiArray.size();
        std::vector<double> delta(hiArray.size());
        std::transform(hiArray.begin(), hiArray.end(), delta.begin(), [mean](double x) { return x - mean; });
        double squareSum = std::inner_product(delta.begin(), delta.end(), delta.begin(), 0.0);
        double stdDeviation = std::sqrt(squareSum / hiArray.size());
        if (std::abs(newVal - mean) > STANDARD_DEVIATION_FILTER * stdDeviation)
        {
            return false;
        }
        hiArray.insert(hiArray.begin(), newVal);
        if(hiArray.size() >= SAMPLE_SIZE)
        {
            hiArray.pop_back();
        }
        return true;
    }
    bool addLoTemp(double newVal)
    {
        if (loArray.size() == 0)
        {
            loArray.push_back(newVal);
            return true;
        }
        double sum = std::accumulate(loArray.begin(), loArray.end(), 0.0);
        double mean = sum / loArray.size();
        std::vector<double> delta(loArray.size());
        std::transform(loArray.begin(), loArray.end(), delta.begin(), [mean](double x) { return x - mean; });
        double squareSum = std::inner_product(delta.begin(), delta.end(), delta.begin(), 0.0);
        double stdDeviation = std::sqrt(squareSum / loArray.size());
        if (std::abs(newVal - mean) > STANDARD_DEVIATION_FILTER * stdDeviation)
        {
            return false;
        }
        hiArray.insert(hiArray.begin(), newVal);
        if(loArray.size() >= SAMPLE_SIZE)
        {
            loArray.pop_back();
        }
        return true;
    }
};

void readSensorSet(Zone* zone);
double getTemp(uint8_t addr[8]);

Zone tempSensor[]{
    {"Top", {0x28,0xFF,0x0D,0x4C,0x05,0x16,0x03,0xC7}, {0x28,0xFF,0x25,0x1A,0x01,0x16,0x04,0xCD}}, 
    {"Mid", {0x28,0xFF,0x89,0x19,0x01,0x16,0x04,0x57}, {0x28,0xFF,0x21,0x9F,0x61,0x15,0x03,0xF9}}, 
    {"Bot", {0x28,0xFF,0x16,0x6B,0x00,0x16,0x03,0x08}, {0x28,0xFF,0x90,0xA2,0x00,0x16,0x04,0x76}}
};

Zone* Top = &tempSensor[0];
Zone* Middle = &tempSensor[1];
Zone* Bottom = &tempSensor[2];

Zone* sensorSet[] = {Top, Middle, Bottom};

double Qtot;

const int pinOneWire = D2;
const int pinLED = D7;

DS18B20 ds18b20(pinOneWire);

void setup() 
{
  Serial.begin(9600);
  
  for (auto set : tempSensor)
  {
      Particle.variable(String(set.sensorName) + "H", set.hiTemp);
      Particle.variable(String(set.sensorName) + "L", set.loTemp);
  }
  Particle.variable("ECO-Qtot", Qtot);
}

void loop() 
{
  double accumulatedEnergy = 0;
  for (auto& set : tempSensor)
  {
      int attempts = 0;
      double newTemp;
      do {
          newTemp = getTemp(set.hiSensor);
          attempts++;
      } while (!set.addHiTemp(newTemp) and (attempts < 5));
      set.hiTemp = set.hiArray[0];
      
      attempts = 0;
      do{
          newTemp = getTemp(set.loSensor);
          attempts++;
      } while (!set.addLoTemp(newTemp) and (attempts < 5));
      set.loTemp = set.loArray[0];
      
      set.avgTemp = (set.hiTemp + set.loTemp) / 2.0;
      set.energy = (set.avgTemp - set.loTemp) * 110 * 1.163 / 1000;
      accumulatedEnergy += set.energy;
  }
  Qtot = accumulatedEnergy;
  delay(5000);
}

double getTemp(uint8_t addr[8]) 
{
    static const int MAXRETRY = 3;
    double _temp;
    int i = 0;

    do {
         _temp = ds18b20.getTemperature(addr);
    } while (!ds18b20.crcCheck() && MAXRETRY > i++);

    if (i < MAXRETRY) 
    {
        //celsius = _temp;
        //fahrenheit = ds18b20.convertToFahrenheit(_temp);
        Serial.println(_temp);
    }
    else 
    {
        _temp = NAN;
        //celsius = fahrenheit = NAN;
        Serial.println("Invalid reading");
    }
    return _temp;
}

I was too time constrained (lazy) to reduce the two member functions to one. :wink: