WBC Beehive data logger

Why?

As someone who spends much time away from home I appreciate the ability to monitor things remotely, whether this is for setting the home temperature for creature comforts, for security and peace of mind, or for general interest.  This project firmly falls into the latter, but also just because I wanted to have a play.

Of course that being the case it is also convenient to sense a colony's health without invasive inspection and enables me to judge when and if to feed thereby increasing the chance of it surviving the winter.  It also gives me knowledge that the colony is in fact alive - or not.

I decided to install a Raspberry Pi Original Model B (256MB Ram) connected to the mains with a WiFi USB dongle. Two thermometer probes and an A-D converter (HX711) are soldered to a 'ModMyPi - Humble Pi V1.2' inside a Maplin plastic enclosure. The temperature probes are in the centre of the brood box and in ambient shade and the A-D converter measures the hive's weight as it props up the rear of the base.

I have connected a camera to attempt to count bees in and out of the hive, and although initially successful the camera was damaged by moisture and I am in the process of improving the design.

Future modification will be to include off-grid power using solar cells and a battery linked to a UPS board available for the Pi, and to explore 3G/4G connectivity also. A sunshine sensor might also be included if they survive the winter.

I hope you enjoy the detail I enclose here, if you have any questions, I would be happy to receive them. My email is at the bottom of the page.

Placeholder Picture

Dimensions to measure and components

Temperature

The most easy to measure is temperature, this also gives a good idea as to whether the hive is alive.  During the summer the colony will maintain itself around 32C, burning energy and clustering if cool and faning air through the hive or spreading out (bee beard) if hot.  This is necessary to reliably rear brood which requires this temperature to survive.  During the winter months there is no need to rear brood and the colony clusters into a ball with the queen in the centre.  No heat is wasted on keeping the whole hive warm and although the queen remains warm, those workers around her will generate heat and exchange places with those on the outside to increase the chances of survival.  In this way it is possible for a colony to withstand deep sub-zero temperatures as is experienced in central USA.

Placeholder Image
Placeholder Image

Hive Mass

A hive's mass will indicate the current state of the colony stores.  Bees will forage nectar and pollen and store them in the hive's honeycomb as honey and raw pollen.  The beekeeper will steal honey for the harvest but must allow sufficient to remain from which the colony will consume and transfer to energy to keep warm.  Thus the hive mass should increase in the summer and decrease over winter.

Bee Counter

An aspect of beekeeping that I fear is the swarming of the colony.  This leads to the exodus of 50% and if not caught in time could be permanent.  If given notice a keeper may find and collect a static swarm as it sends scouts in search of a new home - this swarm is likely hanging on a nearby branch - and start a new colony thus maintaining honey production and the chance to expand!  Although the hive's mass would indicate such an exodus this measurement is muddied by external factors such as rain, snow, wind, other animals and moisture.  If a bee counter could be constructed it would show directly what the bees are doing.  It is possible to construct a non invasive counter using the graphical processing of a raspberry pi camera and some python code.

Placeholder Image

Construction

Raspberry Pi

A Raspberry Pi model B was chosen for this task partly because it has ethernet connectivity but also because it could store and upload tabulated data and charts to a website directly without another computer.  The Pi's processing power also lends itself to performing graphical analysis as will be required by the bee counting function.  Aside from this I had a spare model B loafing and decided to use it. Power will initially be drawn from the mains and converted using a standard mobile phone charger, and ethernet connectivity through a WiFi USB dongle.

Placeholder Image
Placeholder Image
Placeholder Picture

Thermometer

The device shown here is a sealed digital temperature probe designated DS18B20. It communicates in serial with a raspberry pi or arduino and multiple devices can be linked in parallel without a problem. Live and data must be bridged with a 4.7K Ohm resistor.  It should be connected to the raspberry pi GPIO pins as shown.

Loadcell

A load cell contains a wheatstone bridge which if correctly configured should compensate for temperature change and vary in resistance linearly dependent upon the load applied. An amplifier and analogue to digital converter (HX711) is required to apply a voltage, measure the signal output and convert this into a digital signal for processing on a raspberry pi or arduino.  The HX711 is connected to the pi's GPIO as follows:
VCC - +5V     DAT - GPIO 22     CLK - GPIO 23     GND - Gnd
The Loadcell is connected to the HX711 in accordance with the wiring colour scheme. This will change depending on the load's direction.

Placeholder Picture
Placeholder Image
Placeholder Image

Bee Counter Camera

The Raspberry Pi camera and the Pi's video processing power are ideally suited to this task.  A very handy python script has been generated by a programming genius which I have used to great effect. The development of this code is here and the git hub python script page here, I am endebted to this gentleman.  I did make a few adjustments:

1. In running the script I could not get reliable counts so I drew the hive entrance on the images using opencv. This revealed an issue - it is necessary to reference the top left and bottom right corners of the box from the top right corner of the image.

2. The PI I used is 1st gen model B and a balance had to be struck between the frame speed and resolution owing to the processing power available.  I settled on 640x480 @ 60 FPS.

Of note it is important to waterproof the camera. I initially taped the camera board inside a small plastic box with a cut out underneath. The tape failed and the camera became wet and ceased to work.  I am in the process of recommissioning this feature.

Anciliary Equipment

To hook all devices together I purchased a clear topped waterproof box from Maplin, a cable gland fitting and a ModMyPi Humble Pi V1.2.

Placeholder Image

Programing

Raspbian

The Raspberry Pi is installed with Raspbian. The desktop is not required therefore Raspbian Lite is all that is required.

RRDTools

I have chosen to record and view my data using a recursive database and the best software available to do this simply is RRDTool.  RRDtool is the OpenSource industry standard, high performance data logging and graphing system for time series data.  Recursive databases were set up as follows (please google RRDTool for full details of the parameters):

Termperature database:
pi@raspberrypi:~/hive_data $ cat rrdcreate.sh
rrdtool create hive_data.rrd \
--start now --step 60 \
DS:brood_temp:GAUGE:120:-50:50 \
DS:outside_temp:GAUGE:120:-50:50 \
RRA:AVERAGE:0.5:1:360 \
RRA:AVERAGE:0.5:2:720 \
RRA:AVERAGE:0.5:12:840 \
RRA:AVERAGE:0.5:60:744 \
RRA:AVERAGE:0.5:360:730


Weight database:
pi@raspberrypi:~/hive_data $ cat rrdcreateweight.sh
rrdtool create weight.rrd \
--start now --step 60 \
DS:weight:GAUGE:120:0:100 \
RRA:AVERAGE:0.5:1:12 \
RRA:AVERAGE:0.5:1:288 \
RRA:AVERAGE:0.5:12:168 \
RRA:AVERAGE:0.5:12:720 \
RRA:AVERAGE:0.5:288:365



Bee counter database
pi@raspberrypi:~/hive_data $ cat rrdcreateinnandout.sh
rrdtool create hive_data_inandout.rrd \
--start now --step 60 \
DS:in:COUNTER:120:0:4294967295 \
DS:out:COUNTER:120:0:4294967295 \
RRA:MAX:0.5:1:360 \
RRA:MAX:0.5:2:720 \
RRA:MAX:0.5:12:840 \
RRA:MAX:0.5:60:744 \
RRA:MAX:0.5:360:730

Crontab

Crontab is a feature of Linux that allows periodical processing of user defined commands.  Additional lines for my crontab are below, with a description of each following the '#'. Of note the bee counter software will not be commanded to run on reboot because it is not currently set up. 

#@reboot /usr/bin/python /home/pi/beehive/bees_picamera.py
* * * * * /usr/bin/python /home/pi/hive_data/readtemp.py # this reads DS18B20 thermometers and appends the data to the RRD
1,6,11,16,21,26,31,36,41,46,51,56 * * * * sudo /home/pi/hx711-master/hx711 66002 > /home/pi/hive_data/weight.txt # runs the HX711 script and writes the value to weight.txt
2,7,12,17,22,27,32,37,42,47,52,57 * * * * /usr/bin/python /home/pi/hive_data/readweight.py # runs python script to read weight.txt and inject figure to the RRD
58 * * * * /home/pi/hive_data/inandoutgraph.sh # generates bee counter graphs
58 * * * * /home/pi/hive_data/graph.sh # generates temperature graphs
59 * * * * /home/pi/hive_data/wtgraph.sh # generates weight graphs
0 * * * * /usr/bin/rsync -crt /home/pi/hive_data/*_graphs -e "ssh -p 22" user@server.domain.com:./www/theroadrats/htdocs/
3 0 * * * /usr/bin/rsync -crt /home/pi/hive_data/*.rrd -e "ssh -p 22" user@server.domain.com:./daily_bee_pi_rrd_backup/

DS18B20

The following Python script is run to obtain the temperature data and inject to the RRD.  When connected DS18B20 probes appear as a text file in /sys/bus/w1/devices with a unique serial code.

pi@raspberrypi:~/hive_data $ cat readtemp.py
#!/usr/bin/python

import rrdtool

databaseFile = "/home/pi/hive_data/hive_data.rrd"
MIN_TEMP = -50
ERROR_TEMP = -999.99

rrds_to_filename = {
"brood_temp" : "/sys/bus/w1/devices/28-02158110cbff/w1_slave",
"outside_temp" : "/sys/bus/w1/devices/28-0215812fcfff/w1_slave",
}

def read_temperature(file):
tfile = open(file)
text = tfile.read()
tfile.close()
lines = text.split("\n")
if lines[0].find("YES") > 0:
temp = float((lines[1].split(" ")[9])[2:])
temp /= 1000
print temp
return temp
return ERROR_TEMP

def read_all():
template = ""
update = "N:"
for rrd in rrds_to_filename:
template += "%s:" % rrd
temp = read_temperature(rrds_to_filename[rrd])
update += "%f:" % temp
update = update[:-1]
template = template[:-1]
rrdtool.update(databaseFile, "--template", template, update)

read_all()

HX711

The program to run the A-D converter and amplifier is obtained from: https://github.com/ggurov/hx711
and setup conducted iaw the following website: http://hivetool.org/w/index.php?title=Interface_the_HX711_to_Pi
When installed an executable is periodically run by the crontab daemon. This reads the loadcell voltage and generates a figure that is then written to a file named weight.txt.
The following Python script is then run to convert this raw figure to a calibrated number (19291 is the number of units that represent 1Kg) and this is then inject to the RRD:

pi@raspberrypi:~/hive_data $ cat readweight.py
#!/usr/bin/python

import rrdtool

databaseFile = "/home/pi/hive_data/weight.rrd"

infile = open('/home/pi/hive_data/weight.txt', 'r')
weight = float(infile.readline(6))
infile.close()

weight /= 19291

rrdtool.update('/home/pi/hive_data/weight.rrd', 'N:%s' % (weight))

Software Bee Counter

The following script when run generates a counter against the number of bees that enter and exit the hive entrance (stipulated by the co-ordinates already mentioned). An additional line has been added to update the RRD with this counter each cycle.  This function is still experimental.

pi@raspberrypi:~/beehive $ cat bees_picamera.py
"""
Counts Bees in and bees out of a beehive.
Take as input a top-down view of the behive entrance and assume a fairly static
background.

algo goes as follow :
- build and keep up to date a statistical model of the background
- image substraction + morphological operator to henance the contrast
- blob detection
- blob tracking using basic munkres assignment, should use prediction based on
previous motion
- count IN +1 for each bees from wich track is lost close to the entrance
- count OUT +1 for each bees from wich track appears close to the entrance

# Copyright (c) 2015 Antoine Letouzey antoine.letouzey@gmail.com
# Author: Antoine Letouzey
# LICENSE: LGPL

"""

import picamera
import numpy as np
import math
import cv2
import sys
import traceback

import rrdtool

import io
from collections import deque
from munkresPi import linear_assignment # local import

import time
time.sleep(120) # delays script on boot for 1 minute

#------------

# statistical background model, build a model from previous frames
class BGmodel(object):
def __init__(self, size, shape=(640, 480)):
self.size = size #number of frame used to compute a model
self.hist = np.zeros((self.size, shape[1], shape[0]))
self.model = None
self.cpt = 0
self.ready = False

# add a frame and update the model
def add(self, frame, updateModel = True):
self.hist[self.cpt,:,:] = np.copy(frame)
self.cpt += 1
if self.cpt == (self.size - 1) :
self.ready = True
self.cpt %= self.size
if updateModel:
self.updateModel()

# update the model from the current frame history
def updateModel(self):
# np.mean is faster but median yields better results
#self.model = np.median(self.hist, axis=0).astype(np.int32)
self.model = np.mean(self.hist, axis=0).astype(np.int32)

def getModel(self):
return np.copy(self.model)

# substract the background to the current frame
def apply(self, frame):
self.add(frame)
# *2 to enhance the contrast
res = 2*(np.abs(frame-self.model).astype(np.int32))
res = np.clip(res, 0, 255)
return res.astype(np.uint8)



# -----------------

# convininent Bee object to hold a bee's position, motion history and provides
# drawing functions
class Bee(object):
staticID = 0
def __init__(self, pos):
self.pos = [list(pos)]
self.lastSeen = 0
self.ID = Bee.staticID
Bee.staticID += 1
self.age = 0
self.color = tuple(255*(0.2+4*np.random.random((3))/5))

# move the bee to it's new position
def move(self, pos):
self.age += 1
self.pos.append(list(pos))

# remove last position
def pop(self):
self.age -= 1
self.pos.pop()

# compute distance to a point from given previous position
def dist(self, pt, offset = 0):
return math.sqrt((pt[0]-self.pos[-(1+offset)][0])**2 + (pt[1]-self.pos[-(1+offset)][1])**2)

# draw the bee's path as a line onto an image
def draw(self, img):
#print "drawing bee#%d"%self.ID
cv2.polylines(img, [np.int32(self.pos)], False, self.color)



# -----------------

SCALE = 2 # 1 => (640,480), 2 => (320, 240), 4 => (160, 120)

class Hive(object):
def __init__(self, x, y, w, h):
self.IN = 0 # number of bees that went in
self.OUT = 0 # number of bees that went out
self.x = x # X coordinate of top left corner of the entrance
self.y = y # Y coordinate of top left corner of the entrance
self.w = w # width of the entrance
self.h = h # height of the entrance

# new bee, check if it came from the entrance
def append(self, pt):
# if a bee pops out from the hive entrance, then count it
if pt[0] > self.x and pt[0] < self.x+self.w and pt[1] > self.y and pt[1] < self.y+self.h:
self.OUT += 1

# lost bee, check if it went into the entrance
def remove(self, pt):
# if a bee desapears closer to the hive entrance, then count it
if pt[0] > self.x and pt[0] < self.x+self.w and pt[1] > self.y and pt[1] < self.y+self.h:
self.IN += 1

# draw counters on the image
def draw(self, img):
cv2.putText(img,"IN : %d OUT : %d"%(self.IN, self.OUT), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255),2)
cv2.rectangle(img,(20/SCALE,280/SCALE),(620/SCALE,320/SCALE),(0,255,0),1)





#-------------------------------------------------------------------------------
#
# INPUT and parameters
#
#-------------------------------------------------------------------------------

#------------ DATASET dependent
# scale of the inpute image, must be a divisor of 640 and 480 (see bellow)
#SCALE = 2 # 1 => (640,480), 2 => (320, 240), 4 => (160, 120)

# number of frames used to compute a statistical model of the background,
# more is better but slower
HISTSIZE = 15

# position of the hive entrance rectangle with top left corner (X,Y) and
# size (width, height)
hive = Hive(20./SCALE,280./SCALE,620./SCALE,320./SCALE)

#---------- CAMERA
camera = picamera.PiCamera()
camera.resolution = (640/SCALE, 480/SCALE)
camera.framerate = 60


#------------ GENERIC parameters
# morphological structuring element to clean the image
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
# background model with history size
bgm = BGmodel(HISTSIZE, camera.resolution)
# maximum "jump" a bee can make between two consecutive detections in pixels
THRESHBEE = 60/SCALE


#------------ BLOB detector parametrization

# Setup SimpleBlobDetector parameters.
params = cv2.SimpleBlobDetector_Params()

# Change thresholds
params.filterByColor = False;
#params.minThreshold = 10;
#params.maxThreshold = 255;

# Filter by Area.
params.filterByArea = True
params.minArea = 10/(SCALE*SCALE)
params.maxArea = 400/(SCALE*SCALE)

# Filter by Circularity
params.filterByCircularity = False
params.minCircularity = 0.70

# Filter by Convexity
params.filterByConvexity = True
params.minConvexity = 0.70

# Filter by Inertia
params.filterByInertia = True
params.minInertiaRatio = 0.1
params.maxInertiaRatio = 0.5

# Create a detector with the parameters
ver = (cv2.__version__).split('.')
if int(ver[0]) < 3 :
detector = cv2.SimpleBlobDetector(params)
else :
detector = cv2.SimpleBlobDetector_create(params)



#-------------------------------------------------------------------------------
#
# MAIN LOOP
#
#-------------------------------------------------------------------------------
frameid = -1
bees = []


try :
while True:
# get a new frame from the video file
frameid += 1
stream = io.BytesIO()
camera.capture(stream, format='jpeg', use_video_port=True)
# Construct a numpy array from the stream
data = np.fromstring(stream.getvalue(), dtype=np.uint8)
# "Decode" the image from the array, preserving colour as BGR
frame = cv2.imdecode(data, 1)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray,(5,5),0).astype(np.int32)

# update the model and apply it to the current frame
fgmask = bgm.apply(gray)

# Mathematical morphology to enhance the mask and remove outliers
#fgmask = cv2.erode(fgmask,kernel,iterations = 1)
fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_CLOSE, kernel)


im_with_keypoints = None

# blob detection only if we gathered enough images for the background model
if bgm.ready :
keypoints = detector.detect(fgmask)
# Draw detected blobs as red circles.
im_with_keypoints = np.zeros((480/SCALE,640/SCALE,3))#cv2.drawKeypoints(fgmask, keypoints, np.array([]), (0,0,255))
if len(bees) == 0: # no bees yet, no matching to do, just add them
for kp in keypoints:
bees.append(Bee(kp.pt))
hive.append(kp.pt)
else :
# MUNKRES assignment, slightly better
freeBees = [True for i in xrange(len(bees))]
freeKP = [True for i in xrange(len(keypoints))]
# build cost matrix
cost = np.zeros((len(keypoints), len(bees)))
for i,kp in enumerate(keypoints):
for j,b in enumerate(bees):
cost[i,j] = b.dist(kp.pt)
# proper assignment
assignment = linear_assignment(cost)
for ass in assignment :
if cost[ass[0], ass[1]] < THRESHBEE:
bees[ass[1]].move(keypoints[ass[0]].pt)
freeBees[ass[1]] = False
freeKP[ass[0]] = False
for i in xrange(len(freeBees)): # lost bees
if freeBees[i]:
bees[i].lastSeen += 1
for i in xrange(len(freeKP)): # new keypoints
if freeKP[i]:
bees.append(Bee(keypoints[i].pt))
hive.append(kp.pt)
# remove lost bees (not seen for at least 15 frames)
tmp = []
for b in bees:
if b.lastSeen < 15: # bee still "alive"
tmp.append(b)
b.draw(im_with_keypoints)
b.draw(frame)
else :
hive.remove(b.pos[-1])
bees = tmp
hive.draw(im_with_keypoints)
hive.draw(frame)
# print "frame : %d / IN : %d / OUT : %d"%(frameid, hive.IN, hive.OUT)
rrdtool.update('/home/pi/hive_data/hive_data_inandout.rrd', 'N:%d:%d' % (hive.IN, hive.OUT))
cv2.imwrite("out/frame_%05d.jpg"%frameid, frame)




#debug output, uncomment to see each step
#cv2.imshow('frame',np.concatenate((fgmask, gray2), axis=1))
#cv2.imshow('model',bgm.model.astype(np.uint8))
#cv2.imshow('frame',gray.astype(np.uint8))
#cv2.imshow('mask',fgmask)
#if im_with_keypoints is not None :
# cv2.imshow("Keypoints", im_with_keypoints)
#cv2.imshow('color',frame)

# needs to be uncommented for the images to show properly
#if cv2.waitKey(1) & 0xFF == ord('q'):
# break
except Exception as e:
print e
traceback.print_exc(file=sys.stdout)
# always end with an error, but sequences are too long for me to bother debuging :(
pass
finally :
camera.close()
cv2.destroyAllWindows()

print "IN : ", hive.IN
print "OUT: ", hive.OUT


Graphing Functions

The following scripts generate the graphs. It is necessary to install 'sunwait' which gives sunrise and sunset times given the hives position on the earth.  These graphs are uploaded to my website hourly iaw crontab.

Temperature graph generation:
pi@raspberrypi:~/hive_data $ cat graph.sh
#!/bin/bash

############################
#
# Parameters to adjust
#
############################
RRDPATH="/home/pi/hive_data"
IMGPATH="/home/pi/hive_data/temp_graphs"
RRDFILE="hive_data.rrd"
LAT="53N"
LON="2W"

# Graph Colors
A_COLOUR="#FF9933"
B_COLOUR="#0000FF"
TRENDCOLOUR="#FFFF00"

# Calculating Civil Twilight based on location from LAT LON
DUSKHR=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun rises/{:a;n;/Nautical twilight/b;p;ba}' | cut -c 45-46`
DUSKMIN=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun rises/{:a;n;/Nautical twilight/b;p;ba}' | cut -c 47-48`
DAWNHR=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun rises/{:a;n;/Nautical twilight/b;p;ba}' | cut -c 30-31`
DAWNMIN=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun rises/{:a;n;/Nautical twilight/b;p;ba}' | cut -c 32-33`

# Calculating sunset/sunrise based on location from LAT LON
SUNRISEHR=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun transits/{:a;n;/Civil twilight/b;p;ba}' | cut -c 30-31`
SUNRISEMIN=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun transits/{:a;n;/Civil twilight/b;p;ba}' | cut -c 32-33`
SUNSETHR=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun transits/{:a;n;/Civil twilight/b;p;ba}' | cut -c 45-46`
SUNSETMIN=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun transits/{:a;n;/Civil twilight/b;p;ba}' | cut -c 47-48`

SUNRISEHR=${SUNRISEHR#0}
SUNRISEMIN=${SUNRISEMIN#0}
SUNSETHR=${SUNSETHR#0}
SUNSETMIN=${SUNSETMIN#0}

# Converting to seconds
SUNR=$(($SUNRISEHR * 3600 + 10#$SUNRISEMIN * 60))
SUNS=$(($SUNSETHR * 3600 + $SUNSETMIN * 60))
DUSK=$(($DUSKHR * 3600 + $DUSKMIN * 60))
DAWN=$(($DAWNHR * 3600 + $DAWNMIN * 60))

############################
#
# Creating graphs
#
############################
#hour
rrdtool graph $IMGPATH/hour.png --start -6h --end now \
--title "WBC Hive Temperature - Last 6 Hours" \
-v "Temperature (°C)" \
--full-size-mode \
--width=700 --height=400 \
--slope-mode \
--color=SHADEB#9999CC \
--watermark="© Rem Fowler - 2016" \
DEF:temp1=$RRDPATH/$RRDFILE:brood_temp:AVERAGE \
DEF:temp2=$RRDPATH/$RRDFILE:outside_temp:AVERAGE \
CDEF:nightplus=LTIME,86400,%,$SUNR,LT,INF,LTIME,86400,%,$SUNS,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:nightminus=LTIME,86400,%,$SUNR,LT,NEGINF,LTIME,86400,%,$SUNS,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:nightplus#E0E0E0 \
AREA:nightminus#E0E0E0 \
CDEF:dusktilldawn=LTIME,86400,%,$DAWN,LT,INF,LTIME,86400,%,$DUSK,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:dawntilldusk=LTIME,86400,%,$DAWN,LT,NEGINF,LTIME,86400,%,$DUSK,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:dusktilldawn#CCCCCC \
AREA:dawntilldusk#CCCCCC \
COMMENT:" Location Last Avg\l" \
LINE2:temp1$A_COLOUR:"Brood Box " \
GPRINT:temp1:LAST:"%5.1lf °C" \
GPRINT:temp1:AVERAGE:"%5.1lf °C\l" \
COMMENT:"Dawn\: $DAWNHR\:$DAWNMIN\r" \
LINE2:temp2$B_COLOUR:"Outside " \
GPRINT:temp2:LAST:"%5.1lf °C" \
GPRINT:temp2:AVERAGE:"%5.1lf °C\l" \
COMMENT:"\u" \
COMMENT:"Sunrise\: $SUNRISEHR\:$SUNRISEMIN\r" \
COMMENT:"Sunset\: $SUNSETHR\:$SUNSETMIN\r" \
COMMENT:"Dusk\: $DUSKHR\:$DUSKMIN\r" \
HRULE:0#66CCFF:"freezing\l"

#day
rrdtool graph $IMGPATH/day.png --start -1d --end now \
--title "WBC Hive Temperature - Last Day" \
-v "Temperature (°C)" \
--full-size-mode \
--width=700 --height=400 \
--slope-mode \
--color=SHADEB#9999CC \
--watermark="© Rem Fowler - 2016" \
DEF:temp1=$RRDPATH/$RRDFILE:brood_temp:AVERAGE \
DEF:temp2=$RRDPATH/$RRDFILE:outside_temp:AVERAGE \
CDEF:nightplus=LTIME,86400,%,$SUNR,LT,INF,LTIME,86400,%,$SUNS,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:nightminus=LTIME,86400,%,$SUNR,LT,NEGINF,LTIME,86400,%,$SUNS,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:nightplus#E0E0E0 \
AREA:nightminus#E0E0E0 \
CDEF:dusktilldawn=LTIME,86400,%,$DAWN,LT,INF,LTIME,86400,%,$DUSK,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:dawntilldusk=LTIME,86400,%,$DAWN,LT,NEGINF,LTIME,86400,%,$DUSK,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:dusktilldawn#CCCCCC \
AREA:dawntilldusk#CCCCCC \
COMMENT:" Location Last Avg\l" \
LINE2:temp1$A_COLOUR:"Brood Box " \
GPRINT:temp1:LAST:"%5.1lf °C" \
GPRINT:temp1:AVERAGE:"%5.1lf °C\l" \
COMMENT:"Dawn\: $DAWNHR\:$DAWNMIN\r" \
LINE2:temp2$B_COLOUR:"Outside " \
GPRINT:temp2:LAST:"%5.1lf °C" \
GPRINT:temp2:AVERAGE:"%5.1lf °C\l" \
COMMENT:"\u" \
COMMENT:"Sunrise\: $SUNRISEHR\:$SUNRISEMIN\r" \
COMMENT:"Sunset\: $SUNSETHR\:$SUNSETMIN\r" \
COMMENT:"Dusk\: $DUSKHR\:$DUSKMIN\r" \
HRULE:0#66CCFF:"freezing\l"

#week
rrdtool graph $IMGPATH/week.png --start -1w \
--full-size-mode \
--title "WBC Hive Temperature - Last Week" \
-v "Temperature (°C)" \
--width=700 --height=400 \
--slope-mode \
--color=SHADEB#9999CC \
--watermark="© Rem Fowler - 2016" \
DEF:temp1=$RRDPATH/$RRDFILE:brood_temp:AVERAGE \
DEF:temp2=$RRDPATH/$RRDFILE:outside_temp:AVERAGE \
CDEF:nightplus=LTIME,86400,%,$SUNR,LT,INF,LTIME,86400,%,$SUNS,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:nightminus=LTIME,86400,%,$SUNR,LT,NEGINF,LTIME,86400,%,$SUNS,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:nightplus#E0E0E0 \
AREA:nightminus#E0E0E0 \
CDEF:dusktilldawn=LTIME,86400,%,$DAWN,LT,INF,LTIME,86400,%,$DUSK,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:dawntilldusk=LTIME,86400,%,$DAWN,LT,NEGINF,LTIME,86400,%,$DUSK,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:dusktilldawn#CCCCCC \
AREA:dawntilldusk#CCCCCC \
COMMENT:" Location Last Avg\l" \
LINE2:temp1$A_COLOUR:"Brood Box " \
GPRINT:temp1:LAST:"%5.1lf °C" \
GPRINT:temp1:AVERAGE:"%5.1lf °C\l" \
COMMENT:"Dawn\: $DAWNHR\:$DAWNMIN\r" \
LINE2:temp2$B_COLOUR:"Outside " \
GPRINT:temp2:LAST:"%5.1lf °C" \
GPRINT:temp2:AVERAGE:"%5.1lf °C\l" \
COMMENT:"\u" \
COMMENT:"Sunrise\: $SUNRISEHR\:$SUNRISEMIN\r" \
COMMENT:"Sunset\: $SUNSETHR\:$SUNSETMIN\r" \
COMMENT:"Dusk\: $DUSKHR\:$DUSKMIN\r" \
HRULE:0#66CCFF:"freezing\l"

#month
rrdtool graph $IMGPATH/month.png --start -1m \
--title "WBC Hive Temperature - Last Month" \
-v "Temperature (°C)" \
--full-size-mode \
--width=700 --height=400 \
--slope-mode \
--color=SHADEA#9999CC \
--watermark="© Rem Fowler - 2016" \
DEF:temp1=$RRDPATH/$RRDFILE:brood_temp:AVERAGE \
DEF:temp2=$RRDPATH/$RRDFILE:outside_temp:AVERAGE \
CDEF:nightplus=LTIME,86400,%,$SUNR,LT,INF,LTIME,86400,%,$SUNS,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:nightminus=LTIME,86400,%,$SUNR,LT,NEGINF,LTIME,86400,%,$SUNS,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:nightplus#E0E0E0 \
AREA:nightminus#E0E0E0 \
CDEF:dusktilldawn=LTIME,86400,%,$DAWN,LT,INF,LTIME,86400,%,$DUSK,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:dawntilldusk=LTIME,86400,%,$DAWN,LT,NEGINF,LTIME,86400,%,$DUSK,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:dusktilldawn#CCCCCC \
AREA:dawntilldusk#CCCCCC \
COMMENT:" Location Last Avg\l" \
LINE2:temp1$A_COLOUR:"Brood Box " \
GPRINT:temp1:LAST:"%5.1lf °C" \
GPRINT:temp1:AVERAGE:"%5.1lf °C\l" \
COMMENT:"Dawn\: $DAWNHR\:$DAWNMIN\r" \
LINE2:temp2$B_COLOUR:"Outside " \
GPRINT:temp2:LAST:"%5.1lf °C" \
GPRINT:temp2:AVERAGE:"%5.1lf °C\l" \
COMMENT:"\u" \
COMMENT:"Sunrise\: $SUNRISEHR\:$SUNRISEMIN\r" \
COMMENT:"Sunset\: $SUNSETHR\:$SUNSETMIN\r" \
COMMENT:"Dusk\: $DUSKHR\:$DUSKMIN\r" \
HRULE:0#66CCFF:"freezing\l"

#year
rrdtool graph $IMGPATH/year.png --start -1y \
--full-size-mode \
--title "WBC Hive Temperature - Last Year" \
-v "Temperature (°C)" \
--width=700 --height=400 \
--color=SHADEB#9999CC \
--slope-mode \
--watermark="© Rem Fowler - 2016" \
DEF:temp1=$RRDPATH/$RRDFILE:brood_temp:AVERAGE \
DEF:temp2=$RRDPATH/$RRDFILE:outside_temp:AVERAGE \
CDEF:nightplus=LTIME,86400,%,$SUNR,LT,INF,LTIME,86400,%,$SUNS,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:nightminus=LTIME,86400,%,$SUNR,LT,NEGINF,LTIME,86400,%,$SUNS,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:nightplus#E0E0E0 \
AREA:nightminus#E0E0E0 \
CDEF:dusktilldawn=LTIME,86400,%,$DAWN,LT,INF,LTIME,86400,%,$DUSK,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:dawntilldusk=LTIME,86400,%,$DAWN,LT,NEGINF,LTIME,86400,%,$DUSK,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:dusktilldawn#CCCCCC \
AREA:dawntilldusk#CCCCCC \
COMMENT:" Location Last Avg\l" \
LINE2:temp1$A_COLOUR:"Brood Box " \
GPRINT:temp1:LAST:"%5.1lf °C" \
GPRINT:temp1:AVERAGE:"%5.1lf °C\l" \
COMMENT:"Dawn\: $DAWNHR\:$DAWNMIN\r" \
LINE2:temp2$B_COLOUR:"Outside " \
GPRINT:temp2:LAST:"%5.1lf °C" \
GPRINT:temp2:AVERAGE:"%5.1lf °C\l" \
COMMENT:"\u" \
COMMENT:"Sunrise\: $SUNRISEHR\:$SUNRISEMIN\r" \
COMMENT:"Sunset\: $SUNSETHR\:$SUNSETMIN\r" \
COMMENT:"Dusk\: $DUSKHR\:$DUSKMIN\r" \
HRULE:0#66CCFF:"freezing\l"


Weight graph generation:
pi@raspberrypi:~/hive_data $ cat wtgraph.sh
#!/bin/bash

############################
#
# Parameters to adjust
#
############################
RRDPATH="/home/pi/hive_data"
IMGPATH="/home/pi/hive_data/weight_graphs"
RRDFILE="weight.rrd"
LAT="53N"
LON="2W"

# Graph Colors
A_COLOUR="#006400"
B_COLOUR="#0000FF"
TRENDCOLOUR="#FFFF00"

# Calculating Civil Twilight based on location from LAT LON
DUSKHR=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun rises/{:a;n;/Nautical twilight/b;p;ba}' | cut -c 45-46`
DUSKMIN=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun rises/{:a;n;/Nautical twilight/b;p;ba}' | cut -c 47-48`
DAWNHR=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun rises/{:a;n;/Nautical twilight/b;p;ba}' | cut -c 30-31`
DAWNMIN=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun rises/{:a;n;/Nautical twilight/b;p;ba}' | cut -c 32-33`

# Calculating sunset/sunrise based on location from LAT LON
SUNRISEHR=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun transits/{:a;n;/Civil twilight/b;p;ba}' | cut -c 30-31`
SUNRISEMIN=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun transits/{:a;n;/Civil twilight/b;p;ba}' | cut -c 32-33`
SUNSETHR=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun transits/{:a;n;/Civil twilight/b;p;ba}' | cut -c 45-46`
SUNSETMIN=`/home/pi/sunwait/sunwait sun up $LAT $LON -p | sed -n '/Sun transits/{:a;n;/Civil twilight/b;p;ba}' | cut -c 47-48`

SUNRISEHR=${SUNRISEHR#0}
SUNRISEMIN=${SUNRISEMIN#0}
SUNSETHR=${SUNSETHR#0}
SUNSETMIN=${SUNSETMIN#0}

# Converting to seconds
SUNR=$(($SUNRISEHR * 3600 + $SUNRISEMIN * 60))
SUNS=$(($SUNSETHR * 3600 + $SUNSETMIN * 60))
DUSK=$(($DUSKHR * 3600 + $DUSKMIN * 60))
DAWN=$(($DAWNHR * 3600 + $DAWNMIN * 60))

############################
#
# Creating graphs
#
############################
#hour
rrdtool graph $IMGPATH/wthour.png --start -6h --end now \
--title "WBC Hive Mass - Last 6 Hours" \
-v "Mass (Kg)" \
--full-size-mode \
--width=700 --height=400 \
--slope-mode \
--color=SHADEB#9999CC \
--watermark="© Rem Fowler - 2016" \
DEF:temp1=$RRDPATH/$RRDFILE:weight:AVERAGE \
CDEF:nightplus=LTIME,86400,%,$SUNR,LT,INF,LTIME,86400,%,$SUNS,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:nightminus=LTIME,86400,%,$SUNR,LT,NEGINF,LTIME,86400,%,$SUNS,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:nightplus#E0E0E0 \
AREA:nightminus#E0E0E0 \
CDEF:dusktilldawn=LTIME,86400,%,$DAWN,LT,INF,LTIME,86400,%,$DUSK,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:dawntilldusk=LTIME,86400,%,$DAWN,LT,NEGINF,LTIME,86400,%,$DUSK,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:dusktilldawn#CCCCCC \
AREA:dawntilldusk#CCCCCC \
COMMENT:" Last Avg\l" \
COMMENT:"\u" \
COMMENT:"Dawn\: $DAWNHR\:$DAWNMIN\r" \
LINE2:temp1$A_COLOUR:"Hive Weight " \
GPRINT:temp1:LAST:"%5.1lf Kg" \
GPRINT:temp1:AVERAGE:"%5.1lf Kg\l" \
COMMENT:"\u" \
COMMENT:"Sunrise\: $SUNRISEHR\:$SUNRISEMIN\r" \
COMMENT:"Sunset\: $SUNSETHR\:$SUNSETMIN\r" \
COMMENT:"Dusk\: $DUSKHR\:$DUSKMIN\r" \

#day
rrdtool graph $IMGPATH/wtday.png --start -1d --end now \
--title "WBC Hive Mass - Last Day" \
-v "Mass (Kg)" \
--full-size-mode \
--width=700 --height=400 \
--slope-mode \
--color=SHADEB#9999CC \
--watermark="© Rem Fowler - 2016" \
DEF:temp1=$RRDPATH/$RRDFILE:weight:AVERAGE \
CDEF:nightplus=LTIME,86400,%,$SUNR,LT,INF,LTIME,86400,%,$SUNS,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:nightminus=LTIME,86400,%,$SUNR,LT,NEGINF,LTIME,86400,%,$SUNS,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:nightplus#E0E0E0 \
AREA:nightminus#E0E0E0 \
CDEF:dusktilldawn=LTIME,86400,%,$DAWN,LT,INF,LTIME,86400,%,$DUSK,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:dawntilldusk=LTIME,86400,%,$DAWN,LT,NEGINF,LTIME,86400,%,$DUSK,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:dusktilldawn#CCCCCC \
AREA:dawntilldusk#CCCCCC \
COMMENT:" Last Avg\l" \
COMMENT:"\u" \
COMMENT:"Dawn\: $DAWNHR\:$DAWNMIN\r" \
LINE2:temp1$A_COLOUR:"Hive Weight " \
GPRINT:temp1:LAST:"%5.1lf Kg" \
GPRINT:temp1:AVERAGE:"%5.1lf Kg\l" \
COMMENT:"\u" \
COMMENT:"Sunrise\: $SUNRISEHR\:$SUNRISEMIN\r" \
COMMENT:"Sunset\: $SUNSETHR\:$SUNSETMIN\r" \
COMMENT:"Dusk\: $DUSKHR\:$DUSKMIN\r" \

#week
rrdtool graph $IMGPATH/wtweek.png --start -1w --end now \
--title "WBC Hive Mass - Last Week" \
-v "Mass (Kg)" \
--full-size-mode \
--width=700 --height=400 \
--slope-mode \
--color=SHADEB#9999CC \
--watermark="© Rem Fowler - 2016" \
DEF:temp1=$RRDPATH/$RRDFILE:weight:AVERAGE \
CDEF:nightplus=LTIME,86400,%,$SUNR,LT,INF,LTIME,86400,%,$SUNS,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:nightminus=LTIME,86400,%,$SUNR,LT,NEGINF,LTIME,86400,%,$SUNS,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:nightplus#E0E0E0 \
AREA:nightminus#E0E0E0 \
CDEF:dusktilldawn=LTIME,86400,%,$DAWN,LT,INF,LTIME,86400,%,$DUSK,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:dawntilldusk=LTIME,86400,%,$DAWN,LT,NEGINF,LTIME,86400,%,$DUSK,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:dusktilldawn#CCCCCC \
AREA:dawntilldusk#CCCCCC \
COMMENT:" Last Avg\l" \
COMMENT:"\u" \
COMMENT:"Dawn\: $DAWNHR\:$DAWNMIN\r" \
LINE2:temp1$A_COLOUR:"Hive Weight " \
GPRINT:temp1:LAST:"%5.1lf Kg" \
GPRINT:temp1:AVERAGE:"%5.1lf Kg\l" \
COMMENT:"\u" \
COMMENT:"Sunrise\: $SUNRISEHR\:$SUNRISEMIN\r" \
COMMENT:"Sunset\: $SUNSETHR\:$SUNSETMIN\r" \
COMMENT:"Dusk\: $DUSKHR\:$DUSKMIN\r" \

#month
rrdtool graph $IMGPATH/wtmonth.png --start -1m --end now \
--title "WBC Hive Mass - Last Month" \
-v "Mass (Kg)" \
--full-size-mode \
--width=700 --height=400 \
--slope-mode \
--color=SHADEB#9999CC \
--watermark="© Rem Fowler - 2016" \
DEF:temp1=$RRDPATH/$RRDFILE:weight:AVERAGE \
CDEF:nightplus=LTIME,86400,%,$SUNR,LT,INF,LTIME,86400,%,$SUNS,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:nightminus=LTIME,86400,%,$SUNR,LT,NEGINF,LTIME,86400,%,$SUNS,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:nightplus#E0E0E0 \
AREA:nightminus#E0E0E0 \
CDEF:dusktilldawn=LTIME,86400,%,$DAWN,LT,INF,LTIME,86400,%,$DUSK,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:dawntilldusk=LTIME,86400,%,$DAWN,LT,NEGINF,LTIME,86400,%,$DUSK,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:dusktilldawn#CCCCCC \
AREA:dawntilldusk#CCCCCC \
COMMENT:" Last Avg\l" \
COMMENT:"\u" \
COMMENT:"Dawn\: $DAWNHR\:$DAWNMIN\r" \
LINE2:temp1$A_COLOUR:"Hive Weight " \
GPRINT:temp1:LAST:"%5.1lf Kg" \
GPRINT:temp1:AVERAGE:"%5.1lf Kg\l" \
COMMENT:"\u" \
COMMENT:"Sunrise\: $SUNRISEHR\:$SUNRISEMIN\r" \
COMMENT:"Sunset\: $SUNSETHR\:$SUNSETMIN\r" \
COMMENT:"Dusk\: $DUSKHR\:$DUSKMIN\r" \
VDEF:slope=temp1,LSLSLOPE \
VDEF:cons=temp1,LSLINT \
CDEF:trend=temp1,POP,slope,COUNT,*,cons,+ \
LINE2:trend#ff0000:"trend":dashes=8 \

#year
rrdtool graph $IMGPATH/wtyear.png --start -1y --end now \
--title "WBC Hive Mass - Last Year" \
-v "Mass (Kg)" \
--full-size-mode \
--width=700 --height=400 \
--slope-mode \
--color=SHADEB#9999CC \
--watermark="© Rem Fowler - 2016" \
DEF:temp1=$RRDPATH/$RRDFILE:weight:AVERAGE \
CDEF:nightplus=LTIME,86400,%,$SUNR,LT,INF,LTIME,86400,%,$SUNS,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:nightminus=LTIME,86400,%,$SUNR,LT,NEGINF,LTIME,86400,%,$SUNS,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:nightplus#E0E0E0 \
AREA:nightminus#E0E0E0 \
CDEF:dusktilldawn=LTIME,86400,%,$DAWN,LT,INF,LTIME,86400,%,$DUSK,GT,INF,UNKN,temp1,*,IF,IF \
CDEF:dawntilldusk=LTIME,86400,%,$DAWN,LT,NEGINF,LTIME,86400,%,$DUSK,GT,NEGINF,UNKN,temp1,*,IF,IF \
AREA:dusktilldawn#CCCCCC \
AREA:dawntilldusk#CCCCCC \
COMMENT:" Last Avg\l" \
COMMENT:"\u" \
COMMENT:"Dawn\: $DAWNHR\:$DAWNMIN\r" \
LINE2:temp1$A_COLOUR:"Hive Weight " \
GPRINT:temp1:LAST:"%5.1lf Kg" \
GPRINT:temp1:AVERAGE:"%5.1lf Kg\l" \
COMMENT:"\u" \
COMMENT:"Sunrise\: $SUNRISEHR\:$SUNRISEMIN\r" \
COMMENT:"Sunset\: $SUNSETHR\:$SUNSETMIN\r" \
COMMENT:"Dusk\: $DUSKHR\:$DUSKMIN\r" \