4th annual Bourbon and Tobacco Tour of KY

The 2021 edition of the Bourbon and Tobacco Tour is planned for April 24-25.

Good pavement, nice gravel, light vehicle traffic, courteous drivers, and the hills of western Kentucky in the springtime – what’s not to like? Well, your legs might complain about some of the gradients, but the descents make it all worthwhile. And the riding companions – unparalleled.

A few updates for the COVID mess:

Rather than meeting at my house for dinner after the Saturday ride, we’ll just pick up dinner in Cadiz and meet at the park on the west side of town to eat and tell biking lies. My wife will be much happier with this, and there’s plenty of parking and tables for us. Because I don’t know what will transpire between now and the ride, please bring a mask and put it in your jersey pocket.

There are plenty of places to pick up some food – pizza at Casey’s, mexican at El Bracera, Sonic, Wendy’s, Taco Bell, KFC, Triplets, Cracker Barrel, and Subway. I’d rather have everyone at my house for a cookout, but I’m trying to make some adjustments that will work for everyone. I appreciate your understanding.

Friday dinner, April 23:

For those folks coming into town on Friday, we’ll meet at Triplett’s BBQ in Cadiz around 6pm for dinner. The restaurant is on US68/KY80 just west of I-24 at exit 65.

Saturday, April 24:

We will meet at 9:30am at the LBL Golden Pond Visitor Center on the Woodlands Trace Road, at the intersection of US-68/KY-80. There is plenty of parking at the visitor center. I will have some repair/adjustment tools, grease, chain lube, floor pump, work stand, and some spare tubes and patches. Hopefully no major repairs will be needed before, during, or after the ride but we’ll be prepared for the usual (and some of the unexpected). We’ll roll out from the parking area around 10am.

We will have a SAG driver again this year. It was a real luxury to have Greg volunteer his time and gas to follow us around in LBL, and he’s planning to join us again.

There is no cost for these rides, but if you wanted to kick in a few dollars for our SAG driver’s gas or a bite to eat for him I’m sure he would appreciate it.

The route:

The route is the same as the 2020 ride, and is entirely within the LBL. This loop starts and ends at the Golden Pond Visitor Center in LBL, and is about 56 miles, all paved, with about 3500 feet of climbing. There are a small number of actual turns in this route, so hopefully there won’t be any issues with navigation or getting lost. I will give my cell number to riders, should assistance be needed during the ride. Cell coverage isn’t always the best in LBL, so bear that in mind. This is an unsupported ride, but we will do our best to help everyone finish up safely.

 

 

Click to Download Cue Sheet for the full route

Click to Download Cue Sheet for the partial route

Other cool stuff to do:

If you want to come in a day early, or stay over for a day or two, you could follow the Kentucky Bourbon Trail. If you like bourbon and want to sample the offerings by several distilleries, this is a great way to do it. Check out where the distilleries are located and plan your route.

You can drive north on the Trace Road, and drive through the Elk and Bison Sanctuary. There’s a small fee at the entrance to the sanctuary, and it’s well worth it. I’ve been there more than once and it’s a unique opportunity to see herds of bison and elk moving around the area. You do have to be in a vehicle – a bicycle vs a 2000 pound bison – no thanks.

There is a trap range close to the Lake Barkley Lodge, so if folks are interested we can shoot some clay targets. No prizes, just bragging rights. I have shotguns, ammunition, and clay targets – so all you’ll need is hearing and eye protection. If you’ve not done this before, it’s challenging and fun. If you’re new to this, I’ll be happy to help you shoot safely.

Post-ride cookout:

See the COVID notes above for Saturday dinner plans.

Sunday, April 25 – ride the trails:

If folks are interested, we’ll take some fatter-tired bikes out for some trail riding on Sunday. There’s a lot to choose from, the map links below will give you an idea of the variety available to us. A good choice is the Hardwoods Trail, from east to west all the way to Kentucky Lake. Mostly crushed limestone and a lot of fun. I would rate this trail as easy/moderate. Bikes with road tires won’t be the best choice for this – wider tires are the best way to go.

A local friend and riding buddy will lead the gravel ride on Sunday, he knows the gravel routes in LBL much better than I do.

At the north end of the LBL, there is a good singletrack loop named the Canal Loop. I would rate this loop as moderate/difficult.

There is a fairly new option, the trails in Livingston County, to the north of LBL. I’ve ridden there a few times and it’s a lot of fun. These trails are rated intermediate/difficult, so bring your “A” game. Be advised, you will want some tread on your tires for these trails.

We’ll figure out when and where to meet during the cookout and I’ll update the information here.

LBL Hike and Bike Trails

LBL Maps – click the Trails tab.

Livingston County MTB Trails

There are 500 miles of trails and 200 miles of roads in LBL. It is great to have this area so close to home. Not all of the trails are available for bicycling, so check the website while you scout a potential route.

 

 

Temperatures to expect:

The average high temperature in April is in the upper 60s, and a low average in the upper 40s, so pretty good riding weather.

Lodging:

Here are some options for lodging and some suggestions for restaurants in the area.

Kenlake State Resort Park Reservations – this is a good choice if you want to ride to the start

Lake Barkley Lodge reservations – there is now a bike path paralleling us-68/KY-80 to the west so it’s a good ride to the start

Both are reasonably priced and are just a few minutes from our starting place. Kenlake and Lake Barkley both have restaurants too. For those folks arriving on Friday, we’ll meet for dinner.

Red Roof Inn – Cadiz – this is about a 15-20 minute drive to the start

Quality Inn – Cadiz – this is about a 15-20 minute drive to the start

Both of these hotels are at I-24 exit 65, and are within walking distance of Triplets. Driving time to the start point is 20-30 minutes.

Bike Shop:

Bikes and Moore in Hopkinsville is a great shop that I’m happy to call “home”. I couldn’t find a complaint if I tried. Good folks and they’ll be happy to help you out, should you need more than a tweak or two. They have knowledgeable mechanics and a good parts inventory too if it comes to that.

Questions:

Contact me if you have any questions, I’ll do my best to help.

Please watch your speed while driving in LBL, it’s federal land, so speeding tickets are expensive – paraphrasing Agent K – “the rangers do not have a sense of humor they’re aware of.”

Raspberry Pi project – a milestone

After updating the humidity correction factors and clearing the log, I’ve now got a month’s worth of temperature and humidity data in the logs.

You can see that the upstairs temperature was much higher at the beginning of the period.  (click on the image to see the full size version)  That’s because I was there and was running electric heaters upstairs.  The temperature began to fall while I was getting ready to head home and turned them off – they are unplugged when I’m not there.  Seems smart to not risk an electrical problem and a fire when there’s no need for heating.

You can also see the daily temperature swings, more pronounced on some days than others.  The swings are larger when the sun is out, as you would expect.  The downstairs swings are not as big, because the windows have blinds (and there are only 2 windows downstairs vs 5 windows upstairs) so solar heating has less of a chance to warm things up.

The humidity is still higher than expected – I’ll need to recheck the sensors with the hygrometer and see if the numbers are still close.  Even if I wind up making a change to the humidity correction factors I don’t plan to restart logging.

Yes, it’s pretty cold inside right now.  That’s what happens when the only heat is a portable electric heater.

I’m sure you won’t be surprised by this, but I also made a couple of tweaks to the heat and cold alert functions in the python logger script.  When a heat or cold alert is rescinded, the email message includes the amount of time the alert was in effect.  Just a little more polish to the project.

Raspberry Pi project – tweaks

My temperature monitoring project has been running very well, especially since I corrected the humidity sensor readings by applying a correction factor.  And the upstairs temperatures did drop below freezing a couple of days ago, so the tasks of shutting off the pump, draining the plumbing, and adding antifreeze in the traps were absolutely worthwhile.

But I decided that it would be nice to know when some threshold temperatures were reached without checking the web page.  I added code to the python3 logger script to write a file when a cold or heat threshold was reached.  It also sends an alert email message when this happens.  The web page that displays the temperature and humidity graphs looks for this file and indicates that a threshold has been crossed for cold or hot temperatures.

It seems the DHT22 sensors can waver back and forth a few tenths of a degree, and if that happens right at one of your threshold values, you will get an annoying collection of emails.  To address this, I implemented a buffer mechanism.  Now, the heat/cold alert will only happen when the temperature crosses the threshold and stays there for 3 consecutive checks.  Since I’m logging the values every 5 minutes, that means the temperature must remain either above (heat) or below (cold) for 15 minutes before an alert is sent.  When the temperature rises above the cold threshold, it must remain above the threshold for 3 consecutive checks before the alert is rescinded.  Rescinding a heat threshold works the same way.

I also added some code to rescind any existing cold or heat alerts when the logging daemon is restarted.  Since you can edit the threshold temperatures, it seems reasonable to start clean.  If an alert should be issued, it will take just a few minutes to get past the buffer mechanism.

So far this is working well.  On my next trip up there, I’ll recheck the sensors using the hygrometer and see if any alterations to the correction factors are needed.  If they are, it’s a trivial change to make.

Video security system – a problem

I’ve installed the two indoor cameras, and unfortunately there’s a problem with the upstairs camera.  This is what the image looks like right before it goes completely black.  The IR emitters aren’t working.  So at night, the image is completely black.  Not terribly useful.

I used the reset link on the camera’s web page, but that’s a software reset, not a hardware reset.  I’m sorry to say that didn’t resolve the problem.

I contacted Amcrest, and explained my problem.  The response suggested that I would need to do a hardware reset, since the software reset didn’t help.  To do that you need to disassemble the camera so that you can access the motherboard, and press a switch on the motherboard.  That will reset the login password, IP address, and video stream configuration.  A nuisance for sure, but maybe it will resolve the problem.  Honestly, I have my doubts – and can I trust a camera that required a hardware reset to restore proper functionality?  When will the next hardware reset be required – a week or a month later?  I had him send me an email with the hardware reset instructions – disassembling the camera voids the warranty, so I wanted some way to demonstrate that I took it apart at their direction should there be a future problem with it.

I’m 5 hours away from the cabin, so I don’t just run up there to try every resolution immediately.  I’ll try the hardware reset on my next trip.  If it works, great – but as I said earlier – it will still be suspect.  If not, I have a plan B.

I’ll connect both uninstalled cameras to the POE switch with patch cables and make sure that they work properly and that the IR emitters are behaving.  I hope that both remaining cameras work properly and I’ll pick one and replace the upstairs camera with it.  I’ll need to reset the IP address, which is easy enough, and verify the video stream parameters as well.  Then I’ll have both indoor cameras working properly, and I can install the remaining good camera in it’s place on the roof soffit.  The bad one will be sent back for a replacement under warranty.  It’s not a difficult install, mounting the camera is pretty easy.  Running the cat6 cable is a little more involved but again not too bad.  Since I borrowed a cable tester from a friend (thanks Bob) I can make sure that the cable connectors are properly crimped on and that the cable is good.  Then I plug it into the POE switch and set it up in ZM.

I’ll leave this camera in Monitor mode for now, since it’s an outdoor camera I’ll need to set up detection zones and ignore zones to try and minimize false positives.  I don’t expect this will be a set-it-and-forget-it process, I think I’ll spend some time tweaking this before it’s reliable.

I’m curious what will show up when I change the mode to Modect and start recording events.

Raspberry Pi project – some conclusions

After making the changes discussed earlier and monitoring the values and the graphs of those values, I can draw some conclusions about the interior conditions at the cabin.

First, conditions downstairs are more stable than the upstairs, both temperature and humidity do not fluctuate nearly as much as they do upstairs.  Makes sense as there are only two small windows downstairs, while the upstairs has five windows.

Second, it is cooler upstairs than downstairs – but a larger difference than I would have expected.  It is 4-5 degrees cooler upstairs, and more humid by 4-5 percent.  Some of the humidity difference is attributable to the temperature difference, but not all of it.  Solar heating during the day does warm the upstairs more than the downstairs – presuming that the sun can supply the solar energy.  On cloudy days, you’re on your own.

But my main interest was to see how close to freezing it gets inside, with no heat source to maintain a set temperature.  I can’t risk frozen pipes, so I drain the water system and put RV antifreeze in the traps and the toilets.  Maybe I’ve been wasting time and RV antifreeze when it isn’t really needed, but now I have a reasonably reliable way to monitor the situation.

Raspberry Pi project – correction factors

As I mentioned earlier, I was suspect of the humidity values returned by the DHT22 sensors.  So I used a hygrometer to get a separate reading – not that the hygrometer is perfect, but my experience over the past couple of weeks was that it agreed with another hygrometer, the temperature and humidity were consistent, and believable for cool winter conditions.

I placed the hygrometer close to the sensors and gave it a couple of hours to settle in.  The temperature matched within a degree, so that’s great.  Not so great on the humidity side, I applied a correction factor of -16 to get the reported values in the ballpark.  Now the sensors are both reporting values that match the hygrometer within a percent.  A significant improvement.

To move one of the sensors upstairs, I took some cat6 cable and soldered the twisted pairs together to make a 4 conductor cable.  One conductor won’t be used with the DHT22 sensors, but I will need it for the BME280 sensors when I swap them.  I ran the cable upstairs, which took a lot longer than it took to type this sentence.  It’s a long story, don’t ask.  Connected everything up and booted the pi.

Upstairs temperature was fine, but the humidity value (again) was off the charts.  So I decided to try the +5v pin instead of the +3.3v pin on the pi for the upstairs sensor, as the connecting wire is now about 15 feet long.  The humidity value came back to reality and has stayed that way for a few days now.

I’ll leave the DHT22 sensors in place for now and see how they behave.  I am definitely going to swap the sensors, but there are higher priority tasks to do, especially since the current sensors seem to be behaving for now.

Raspberry Pi project – swapping sensors, part 2

Now that I’ve laid out my reasons for trying a different type of sensor, let’s dig in.  We need a couple of libraries that the DHT22 sensors didn’t need, so we should go ahead and get them installed.

First, let’s install the smbus2 library. At a command prompt, type:

sudo pip install smbus2

Now, let’s install the library for the SME280 sensor. At a command prompt, type:

sudo pip install RPi.bme280

That takes care of the additional libraries we’ll need for the python script.

Now, let’s connect the sensors to the Raspberry Pi. I’m using the model 4 rev B, so I have a 40-pin GPIO header. We’re going to use six pins to connect our two sensors. We’re going to connect each sensor to it’s own 3.3v source and it’s own ground. We’ll use RPi4 pins 1 and 6 for 3.3v and ground for the upstairs sensor, and pins 17 and 9 for 3.3v and ground for the downstairs sensor. We’ll connect the two clock lines together and they will connect to RPi4 pin 5; then we’ll connect the two data lines together and they will connect to RPi4 pin 3.  Since the two sensors will have a unique address on the bus, we can connect them electrically but interrogate them separately.

We also need to make a small alteration to one of the sensors to change it’s address on the i2c bus.  In the following image, the red mark indicates the we are removing the connection between the left and center pad, and adding a connection from the center pad to the right pad.  We are only altering one of the two sensor boards in this manner.  The unaltered sensor will have bus address 0x76, and the altered sensor will have address 0x77.  The test script below is coded so that the upstairs sensor is the unaltered one, and the altered sensor is the downstairs sensor.

This sensor test script interrogates the sensors and then displays the returned values separately, followed by a string containing the data. Note that python is sensitive to indentation. I’ve added a line of code to convert the celcius temperature into fahrenheit. This line is optional. The full code for our test script follows.

#!/usr/bin/python3

import time
import smbus2
import bme280

port = 1
upstairs_addr = 0x76
downstairs_addr = 0x77
bus = smbus2.SMBus(port)

calibration_params_upstairs = bme280.load_calibration_params(bus, upstairs_addr)
calibration_params_downstairs = bme280.load_calibration_params(bus, downstairs_addr)

#exit()

while True:
    # interrogate the sensors and supply the calibration data we obtained earlier
    upstairs_data = bme280.sample(bus, upstairs_addr, calibration_params_upstairs)
    downstairs_data = bme280.sample(bus, downstairs_addr, calibration_params_downstairs)

    # the compensated data
    print(upstairs_data.id)
    print(upstairs_data.timestamp)
    print(upstairs_data.temperature)
    ftemp_upstairs = upstairs_data.temperature * 9/5.0 + 32
    print(ftemp_upstairs)
    print(upstairs_data.pressure)
    print(upstairs_data.humidity)

    # in string format
    print(upstairs_data)

    # the compensated data
    print(downstairs_data.id)
    print(downstairs_data.timestamp)
    print(downstairs_data.temperature)
    ftemp_downstairs = downstairs_data.temperature * 9/5.0 + 32
    print(ftemp_downstairs)
    print(downstairs_data.pressure)
    print(downstairs_data.humidity)

    # in string format
    print(downstairs_data)

    time.sleep(15)

Once this is working and successfully retrieving and displaying the sensor data, we’ll move on to the changes needed to our data logger script.

Raspberry Pi project – swapping sensors

I’m a little suspect of the humidity readings I get from the DHT22 sensors.  I suspect that they’re reporting higher humidity than is actually the case.  I did some research and I’m not alone in this concern.  It’s not a huge concern by any means, my primary interest is with temperature, but since I’m logging and graphing the data, why not try to make the data as accurate as possible?

That research led me to the BME280 sensor module.  It communicates through the i2c bus, using an address on the bus to differentiate between multiple devices on the same bus.  The BME280 has two available addresses depending on how a jumper on the module is configured.  Since I’m using two sensors that will work out well.

The difference that you’ll need to deal with first is that the BME280 sensors use four connections to the Pi, not three as the DHT22 sensor does.  So you’ll need an extra wire between the sensor and the Pi.

Normally you’d use a breakout board to easily handle the multiple connections needed, but because I don’t foresee adding other sensors at this point, I’m just going to solder the wires for data and clock together.  I’ll continue to use separate 3.3v and ground connections for each sensor.  If that changes, I can easily add a breakout board later.

The python script that interrogates the sensors and saves the data into the rrdtool database will require some changes.  These sensors have calibration data available, and we’ll use that to be sure the readings are as accurate as possible.  We’ll also need different libraries to use the i2c bus and to communicate with the BME280 sensors.  Both of these libraries can easily be installed using pip.

 

Raspberry Pi project – integration

Ideally, the logging / data storage script would run automatically, as would the graph image generation script.  There are multiple ways to make that happen, and some of them will vary depending on your operating system.

The simplest way is to just run the scripts a a scheduled process using cron.  If you do this, you should remove the endless loop and sleep commands from the scripts.  Since cron will execute the scripts on the schedule you choose, there is no need for the script to sleep and then wake up and run through a loop again.

If you choose to run them through cron, you’ll need to run the logging / data storage script every 5 minutes, so the minute parameter in your cron file will be “*/5”, meaning every 5 minutes.  The other timing parameters will be simply “*”.

For the graph image generation script, you would probably run it once each hour.  To run it at the top of the hour, set the minute value to “0”, and the other parameters to “*”. That means cron will run the script every time the minute is 0 (top of the hour).

Since I like the scripts to run as system services, my scripts have the loop and sleep commands in them.  On a linux operating system using systemd, you’ll create a system service for running the scripts. To do this you need root access – either switch to the root user or use sudo.  Go to the /etc/systemd/system directory, and create a file name temperature_logger.service.  Put the following code in it, adjusting the path to the file as required for your environment.

After=network.service

[Service]
ExecStart=/<path-to-your-script>/temperature_logger.py

[Install]
WantedBy=default.target

This file should be owned by root, with 644 permissions.

To activate this as a system service, type “systemctl enable temperature_logger.service”, followed by “systemctl start temperature_logger.service”. This will now start automatically when the computer is booted. To stop it from running, type “systemctl stop temperature_logger.service”. Note that it will restart if the computer is rebooted. If you don’t want it to start unless you start it, type “systemctl disable temperature_logger.service”.

Setting up the graph image generations as a system service is basically identical, except that the service file will have a different name, and the file name on the ExecStart line will be different.

It is your preference as to how you want to execute these scripts. Other OSes will offer different methods that give the same result.

To rotate the log file, we need to add a file into the /etc/logrotate.d/ directory. The name isn’t critical; I chose “mylogs” to differentiate it from the files added when other packages are installed. Presuming that you left the name of the log file as it was in the example code, here is what you’ll need to add.

/var/log/temp_humidity.log {
    su root adm
    daily
    missingok
    rotate 7
    delaycompress
    compress
    notifempty
    create 644 root adm
    sharedscripts
    postrotate
    endscript
    maxage 7
}

This will keep 7 days worth of log files. The current log file and yesterday’s file are not compressed, the other log files are compressed to save space.

I hope you’re found this little project documentation useful. You could extend it by adding another sensor, perhaps locating this one outdoors. The documentation says that the sensor can be connected to the Raspberry Pi by wires as long as 50 yards, so you have some flexibility as to where you place it.

Raspberry Pi project – pretty graphs

Now that we’re saving data into the rrd database, let’s make some graphs so we can see what happens over time to the temperature and humidity values. I chose to use php for this part of the project, as there is a php module for rrdtool that works quite well. We’ll create 5 graphs, Hourly, Daily, Weekly, Monthly, and Yearly. We can also add text to the graphs to show the maximum, average, and minimum values for the time period being displayed.

#!/usr/bin/php -q
<?php


while (true) {

    $f = fopen("/var/log/temp_humidity.log", "a+");
    fwrite($f, date('Y-m-d H:i:s')." generate graph images begin\n");

    create_graph("/var/www/html/temp/temp-hour.png", "-1h", "Woodsmor Cottage - Hourly", $f);
    create_graph("/var/www/html/temp/temp-day.png", "-1d", "Woodsmor Cottage - Daily", $f);
    create_graph("/var/www/html/temp/temp-week.png", "-1w", "Woodsmor Cottage - Weekly", $f);
    create_graph("/var/www/html/temp/temp-month.png", "-1m", "Woodsmor Cottage - Monthly", $f);
    create_graph("/var/www/html/temp/temp-year.png", "-1y", "Woodsmor Cottage - Yearly", $f);

    $output = null;
    $retval = null;
    exec('scp /var/www/html/temp/*.png xxxx@xxxxxxxxxxxx.com:/var/www/html/temp/.', $output, $retval);
    fwrite($f, date('Y-m-d H:i:s')." images copied to macbook with status $retval\n");
    //echo "Returned with output ".print_r($output, true)."\n";

    fwrite($f, date('Y-m-d H:i:s')." generate graph images complete\n");

    fclose($f);
    sleep(3600);

}

exit;

We start off in the same way as the other scripts, by telling the OS which interpreter should be used. Then we have an endless while loop to create the graph images.  The first thing we do is to log the start of the image creation process, using the same log file that the sensor data is using. Then we call a function “create_graph” that passes four parameters. The first is the full path and filename for the image, the second is time span the graph will cover, the third is the title of the graph, and the final is the handle for the log file. We call it five times, once for each graph image we want to create. Once the images have been created, I’m using SCP (SecureCoPy) to copy the images to the webserver from the pi. You might just serve them from the pi if you want. I have another computer on my local network that is running a webserver, so I decided the pi will just collect the data, save it, generate the images, and then copy them to the webserver.  At the end, we sleep for 3600 seconds (one hour), and then create the images all over again.

function create_graph($output, $start, $title, $f) {
    $date_time = date('F j, Y \a\t g:i a');
    // the : has special significance in graph variables, so it must be escaped
    $date_time = str_replace(':', '\:', $date_time);
    $options = array(
        "--width=800",
        "--height=200",
        "--imgformat=PNG",
        "--slope-mode",
        "--start", $start,
        "--title=$title",
        "--vertical-label=Temperature / Humidity",
        "DEF:utmax=/home/scripts/temp_humidity.rrd:uth_dht22:MAX",
        "DEF:uhmax=/home/scripts/temp_humidity.rrd:uhm_dht22:MAX",
        "DEF:dtmax=/home/scripts/temp_humidity.rrd:dth_dht22:MAX",
        "DEF:dhmax=/home/scripts/temp_humidity.rrd:dhm_dht22:MAX",
        "LINE1:utmax#ff0000:Upstairs Temperature",
        "LINE1:uhmax#ff9999:Upstairs Humidity",
        "LINE1:dtmax#0000ff:Downstairs Temperature",
        "LINE1:dhmax#9999ff:Downstairs Humidity",
        "COMMENT:\\n",
        "COMMENT:Generated ".$date_time
    );

    $ret = rrd_graph($output, $options);
    if (! $ret) {
        fwrite($f, date('Y-m-d H:i:s')." Graph error: ".rrd_error()."\n");
    }
}

?>

This is the function that does all of the heavy lifting. The first thing we do is to build a variable that contains the date and time in an easily-read format so that we can include the text in the graph image. That way we know when the graph was generated. We’ll use that value later.

To pass the options to the rrd_graph function, we build an array of values. We tell it the width and height (in pixels) of the image we want, and the format we want. “slope-mode” says we want a line connecting the data points. The time period is passed as a parameter, something like “-1h”. That means all data will be included starting from one hour ago. The graph title is also passed as a parameter.  There is a vertical label defined, but that is optional.

Then we define the four data fields we want to show on the graph. The first parameter in the DEF line is the variable name we will use in the options array, followed by the full path and name of the rrd file. The next parameter is the variable name used when we defined the rrd database. The fourth and final parameter is MAX, indicating that we want to graph the maximum value.

The four LINE1 parameters lay out the legend for the graph. First is the variable name we assigned in the DEF lines, followed by the rgb color values to describe the way the line color should appear. The final parameter is the label for the legend.

The output path and filename, and options array are passed to the rrd_graph function, and we get back a return code. If the graph function fails, we log the reason.

Here’s the complete script.

#!/usr/bin/php -q
<?php


while (true) {

    $f = fopen("/var/log/temp_humidity.log", "a+");
    fwrite($f, date('Y-m-d H:i:s')." generate graph images begin\n");

    create_graph("/var/www/html/temp/temp-hour.png", "-1h", "Woodsmor Cottage - Hourly", $f);
    create_graph("/var/www/html/temp/temp-day.png", "-1d", "Woodsmor Cottage - Daily", $f);
    create_graph("/var/www/html/temp/temp-week.png", "-1w", "Woodsmor Cottage - Weekly", $f);
    create_graph("/var/www/html/temp/temp-month.png", "-1m", "Woodsmor Cottage - Monthly", $f);
    create_graph("/var/www/html/temp/temp-year.png", "-1y", "Woodsmor Cottage - Yearly", $f);

    $output = null;
    $retval = null;
    exec('scp /var/www/html/temp/*.png xxxx@xxxxxxxxxxxx.com:/var/www/html/temp/.', $output, $retval);
    fwrite($f, date('Y-m-d H:i:s')." images copied to macbook with status $retval\n");
    //echo "Returned with output ".print_r($output, true)."\n";

    fwrite($f, date('Y-m-d H:i:s')." generate graph images complete\n");

    fclose($f);
    sleep(3600);

}

exit;

function create_graph($output, $start, $title, $f) {
    $date_time = date('F j, Y \a\t g:i a');
    // the : has special significance in graph variables, so it must be escaped
    $date_time = str_replace(':', '\:', $date_time);
    $options = array(
        "--width=800",
        "--height=200",
        "--imgformat=PNG",
        "--slope-mode",
        "--start", $start,
        "--title=$title",
        "--vertical-label=Temperature / Humidity",
        "DEF:ut=/home/scripts/temp_humidity.rrd:uth_dht22:MAX",
        "DEF:uh=/home/scripts/temp_humidity.rrd:uhm_dht22:MAX",
        "DEF:dt=/home/scripts/temp_humidity.rrd:dth_dht22:MAX",
        "DEF:dh=/home/scripts/temp_humidity.rrd:dhm_dht22:MAX",
        "LINE1:ut#ff0000:Upstairs Temperature",
        "LINE1:uh#ff9999:Upstairs Humidity",
        "LINE1:dt#0000ff:Downstairs Temperature",
        "LINE1:dh#9999ff:Downstairs Humidity",
        "COMMENT:\\n",
        "COMMENT:Generated ".$date_time
    );

    $ret = rrd_graph($output, $options);
    if (! $ret) {
        fwrite($f, date('Y-m-d H:i:s')." Graph error: ".rrd_error()."\n");
    }
}

?>

Here is the Daily graph image created from this script. You’ll notice that the humidity lines merge partway through the day – that’s when I implemented the correction code in the logging script. Also, note that the downstairs temperature is in centigrade, as I mentioned earlier. When this is running at the cabin, I’ll activate the line of code that converts the downstairs temperature to fahrenheit. I would expect the temperature and humidity values to track fairly closely to each other, but that’s one thing I’ll learn as it accumulates data.

Next, we need to integrate the logging script and the graphing script so that they run automatically.