Parabolic solar hot water heater + Pi

Stuck with a problem in your code? Seek help here.

Parabolic solar hot water heater + Pi

Postby frazelle09 » March 17th, 2016, 12:35 pm

We've been at this ... ater#Costs project for some years now and have decided to try and use the Pi, which we would like to use for temperature readings anyway, to control the rotation of the parabola. We have located Jay Dosher's code and have


Code: Select all to be placed in /tools/

#Version Notes
#39: Brought over from solar robot 7, cleanup of OpenElectrons code (going back to a Pololu controller)
#40: Code optimizations for Raspberry Pi A+
#42: Code cleanup, added debug toggle

from __future__ import print_function
import time, math
import serial, pysolar, datetime
from dual_mc33926_rpi import motors, MAX_SPEED

import os
assert os.path.exists('/tools/inputs/masterinputs.txt')

#digital stuff
import RPi.GPIO as GPIO

#support for storing our values in a config file
import configparser

# for the motor control we need the libraries for this controller:

import os, sys
if (((str(sys.argv[1:])[2:])[:-2]) == "debug"):
   debug = True
   debug = False

#read in all our variables
config = configparser.ConfigParser()"/tools/inputs/masterinputs.txt")
#our latitude
maplat = float(config.get("myvars", "maplat"))
#our longitude
maplon = float(config.get("myvars", "maplon"))
#time limits to keep the robot from doing crazy things
pan_time_limit = int(config.get("myvars", "pan_time_limit"))
tilt_time_limit = int(config.get("myvars", "tilt_time_limit"))

#the lowest angle the IMU and mechanical hardware will reliably support
lowestangle = int(config.get("myvars", "lowestangle"))

#motor speed settings
motor1max_speed = int(config.get("myvars", "motor1max_speed"))
#motor2 is the panning motor
motor2max_speed = int(config.get("myvars", "motor2max_speed"))

#sleep tolerance is the margin by which difference between dawn target and actual heading
#this keeps the robot from moving during the night as the compass shifts
sleep_tolerance = int(config.get("myvars", "sleep_tolerance"))

#This is my magnetic declination *offset*
#If your declination is 11, your offset is -11
MagneticDeclination = float(config.get("myvars", "MagneticDeclination"))

#Calibration of the IMU
HorizontalCalibration = int(config.get("myvars", "HorizontalCalibration"))
AngleOffset = int(config.get("myvars", "AngleOffset"))

#Since the heading can fluctuate, we give a small margin to the compass
#Smaller numbers mean more accurate heading, but motor must go slower
hmargin = int(config.get("myvars", "hmargin"))

#Pololu Motor Stuff
# Set up sequences of motor speeds.
test_forward_speeds = list(range(0, MAX_SPEED, 1)) + [MAX_SPEED] * 200 + list(range(MAX_SPEED, 0, -1)) + [0]
test_reverse_speeds = list(range(0, -MAX_SPEED, -1)) + [-MAX_SPEED] * 200 + list(range(-MAX_SPEED, 0, 1)) + [0]
motors.setSpeeds(0, 0)

#prep the digital ports
GPIO.setup(16, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) #this pin is for the override mode switch
GPIO.setup(19, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) #this pin is for horizon mode, on=do no motor movement at all
GPIO.setup(20, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) #unused but wired
GPIO.setup(21, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) #unused but wired

#These are our global motor speed variables- don't touch
global motor1speed
motor1speed = 0
global motor2speed
motor2speed = 0

#Open the serial port for the IMU
serialport = serial.Serial("/dev/ttyAMA0", 57600, timeout=5)

#Make sure the motors aren't doing anything before we start
motors.setSpeeds(0, 0)

#Calibrate the heading
Declination = MagneticDeclination + HorizontalCalibration

#Give the serial port time to open, so wait 2 seconds

#Parse the IMU string to get tilt, which we don't really use
#Besides making sure the robot hasn't turned on its side
def getcurtilt():
   # The escape character for # is \x23 in hex
   response = serialport.readline()
   words = response.split(b",")
   if len(words) > 2:
         curtilt = float(words[2])
         curtilt = 999
   return curtilt

#Here we will experiment with converting a string into a byte
a_string = '\x23o0 \x23f Python'
by = a_string.encode()

#Get the heading from the IMU
#Translate the IMU from magnetic north to true north since the calcs use true north
def getcurheading():
   curheading = 999
   # The escape character for # is \x23 in hex
   serialport.write(b"\x23o0 \x23f")
   headresponse = serialport.readline()
   words = headresponse.split(b",")
   if len(words) > 2:
         curheading = (float(words[0])) + 180
         if curheading + Declination > 360: curheading = curheading - 360 + Declination
         else: curheading = curheading + Declination
         curheading = 999
   return curheading

#Read the IMU to get the angle of incline (forwards/backwards)
#This is what we use for the solar panels, so we have to switch
#from 0 degrees on the normal axis to 0 degrees on the horizon
def getcurangle():
#   curangle = 999
#   print ("AngleOffset: ", AngleOffset)
# The escape character for # is \x23 in hex
   serialport.write(b"\x23o0 \x23f")
   response = serialport.readline()
   words = response.split(b",")
   if len(words) > 2:
         if ((float(words[1]) -90) * -1) < 89:
            curangle = ((float(words[1]) -90) * -1)
            curangle = 0
         curangle = 999
   return curangle + AngleOffset

#For troubleshooting, we use raw Azimuth from the calc
#Since this is in Azimuth:
# and we need true heading:
# and Azimuth actually is the direction of the shadow, not the sun
from pysolar import solar
def getrawazimuth():
   Azimuth = solar.get_azimuth(maplat, maplon, datetime.datetime.utcnow())
   return Azimuth

#Convert Azimuth (the direction of the shadow, degrees from south)
# to heading, we have to deal with a few cases
def getsolarheading():
   Azimuth = solar.get_azimuth(maplat, maplon, datetime.datetime.utcnow())
   if Azimuth < 0:
      if (Azimuth >= -180):
         solarheading = ((Azimuth * -1) + 180)
      if (Azimuth < -180):
         solarheading = ((Azimuth * -1) - 180)
   if Azimuth >= 0:
      solarheading = Azimuth
   return solarheading

def tomorrow_heading():
   increment_min = 1
   incrementeddatetime = 0
   tomorrow_corrected = 90
   if solar.get_altitude(maplat, maplon, datetime.datetime.utcnow()) < 0:
      while solar.get_altitude(maplat, maplon, (datetime.datetime.utcnow() + datetime.timedelta(minutes=incrementeddatetime))) < 0:
        incrementeddatetime = incrementeddatetime + increment_min
      sunrise_time=(datetime.datetime.utcnow() + datetime.timedelta(minutes=incrementeddatetime))
      tomorrow_heading = solar.get_azimuth(maplat, maplon, sunrise_time)
      if tomorrow_heading < 0:
         if (tomorrow_heading >= -180):
            tomorrow_corrected = ((tomorrow_heading * -1) + 180)
         if (tomorrow_heading < -180):
            tomorrow_corrected = ((tomorrow_heading * -1) - 180)
      if tomorrow_heading >= 0:
         tomorrow_corrected = tomorrow_heading
   return tomorrow_corrected

def getsolarangle():
   solarangle = solar.get_altitude(maplat, maplon, datetime.datetime.utcnow())
   return solarangle

def motor2neg():
   global motor2speed
   if (motor2speed < motor2max_speed):
      motor2speed = motor2speed + 5

def motor2backup():
   motor2speed = 0
   backupsecs = 4
   backup_start_time = datetime.datetime.utcnow()
   while (datetime.datetime.utcnow() < (backup_start_time + datetime.timedelta(seconds=backupsecs))):
      while motor2speed < motor2max_speed:
         motor2speed = motor2speed + 1
#Doesn't do anything right now

def motor2pos():
   global motor2speed
   if (motor2speed < motor2max_speed):
      motor2speed = motor2speed + 5

def motor1raise():
   global motor1speed
   if (motor1speed < motor1max_speed):
      motor1speed = motor1speed + 5
      #raise the panel from the horizon
      #reverse motor speed extends the actuator

def motor1lower():
   global motor1speed
   if (motor1speed < motor1max_speed):
      motor1speed = motor1speed + 5
      #lower the panel to the horizon
      #forward motor speed contracts the actuator

tomorrow_static = tomorrow_heading()
#Here we check to make sure horizon (19) and ovveride (16) digital pins aren't on
#print("GPIO 16 (ovveride) is " + str(GPIO.input(16)))
#print("GPIO 19 (horizon) is " + str(GPIO.input(19)))
if (GPIO.input(16) == False) and (GPIO.input(19) == False): #check to see if the passive mode switch is on
# GPIO 16 is for override and GPIO 19 is for horizon mode

#In this section we rotate as needed
   starttime = datetime.datetime.utcnow()
   if (getcurheading() > getsolarheading()) and (getsolarangle() > 2) and (getcurheading() != 999):
      while (getcurheading() > (getsolarheading() + hmargin)) and (starttime + datetime.timedelta(seconds=pan_time_limit) > datetime.datetime.utcnow()):
         if debug == True:
            print("1: Moving " + str(getcurheading()) + " to " + str(getsolarheading()))
      motors.setSpeeds(0, 0)

   starttime = datetime.datetime.utcnow()
   if (getcurheading() < getsolarheading()) and (getsolarangle() > 2) and (getcurheading() != 999):
      while (getcurheading() < (getsolarheading() - hmargin)) and (starttime + datetime.timedelta(seconds=pan_time_limit) > datetime.datetime.utcnow()):
         if debug == True:
            print("2: Moving " + str(getcurheading()) + " to " + str(getsolarheading()))
      motors.setSpeeds(0, 0)

   starttime = datetime.datetime.utcnow()
   if (getcurheading() > tomorrow_static) and (getsolarangle()<0) and (getcurheading() != 999):
      if (getcurheading() - tomorrow_static) > sleep_tolerance:
         while (getcurheading() > (tomorrow_static + hmargin)) and (starttime + datetime.timedelta(seconds=pan_time_limit) > datetime.datetime.utcnow()):
            if debug == True:
               print("3: Moving " + str(getcurheading()) + " to " + str(tomorrow_static + hmargin))
         motors.setSpeeds(0, 0)

   starttime = datetime.datetime.utcnow()
   if (getcurheading() < tomorrow_static) and (getsolarangle()<0) and (getcurheading != 999):
      if (tomorrow_static - getcurheading()) > sleep_tolerance:
         while (getcurheading() < (tomorrow_static - hmargin)) and (starttime + datetime.timedelta(seconds=pan_time_limit) > datetime.datetime.utcnow()):
            if debug == True:
               print("4: Moving " + str(getcurheading()) + " to " + str(tomorrow_static + hmargin))
         motors.setSpeeds(0, 0)

#In this section we angle the panels as needed
   starttime = datetime.datetime.utcnow()
   if (getcurangle() < getsolarangle()) and (getsolarangle() > lowestangle):
      print("Case 5")
      while (getcurangle() < getsolarangle()) and (starttime + datetime.timedelta(seconds=tilt_time_limit) > datetime.datetime.utcnow()):
         if debug == True:
            print("5: Moving " + str(getcurangle()) + " to " + str(getsolarangle()))
      motors.setSpeeds(0, 0)

   if (getcurangle() > getsolarangle()):
      while (getcurangle() > getsolarangle()) and (getsolarangle() > lowestangle) and (starttime + datetime.timedelta(seconds=tilt_time_limit) > datetime.datetime.utcnow()):
         if debug == True:
            print("6: Moving " + str(getcurangle()) + " to " + str(getsolarangle()))
      motors.setSpeeds(0, 0)

#This is horizon mode- if the GPIO switch for pin 19 is flipped
#we lower the panel to 20 degrees. This is good for installing the solar panel,
#since it keeps the robot from moving any more while we're working on it
if (GPIO.input(19) == True) and (GPIO.input(16) == False):
   while (getcurangle() > 20):
   motors.setSpeeds(0, 0)

#update the databases
os.system('/usr/bin/rrdtool update /tools/sensors/corrected_azimuth_db.rrd --template corr_az N:' + str(getsolarheading()))
os.system('/usr/bin/rrdtool update /tools/sensors/elevation_db.rrd --template elev N:' + str(getsolarangle()))
os.system('/usr/bin/rrdtool update /tools/sensors/actual_elevation_db.rrd --template elev N:' + str(getcurangle()))
os.system('/usr/bin/rrdtool update /tools/sensors/actual_heading_db.rrd --template corr_az N:' + str(getcurheading()))

#update the azimuth graph
os.system('/usr/bin/rrdtool graph /var/www/azimuth_graph.png \
-w 600 -h 120 -a PNG \
--slope-mode \
--start -86400 --end now \
--font DEFAULT:7: \
--title \x22heading in degrees\x22 \
--watermark \x22`date`\x22 \
--vertical-label \x22degrees\x22 \
--right-axis-label \x22 \x22 \
--lower-limit 0 \
--right-axis 1:0 \
--x-grid MINUTE:10:HOUR:1:MINUTE:120:0:%R \
--alt-y-grid --rigid \
DEF:azimuth=/tools/sensors/corrected_azimuth_db.rrd:corr_az:MAX \
DEF:heading=/tools/sensors/actual_heading_db.rrd:corr_az:MAX \
LINE1:azimuth\x23707070:\x22Calculated\x22:dashes \
LINE1:heading\x230000FF:\x22Actual\x22 \
GPRINT:azimuth:LAST:\x22Cur Calc\: %5.2lf\x22 \
GPRINT:heading:LAST:\x22Cur Actual\: %5.2lf\x22 \
GPRINT:azimuth:AVERAGE:\x22Avg\: %5.2lf\x22 \
GPRINT:azimuth:MAX:\x22Max\: %5.2lf\x22 \
GPRINT:azimuth:MIN:\x22Min\: %5.2lf\t\t\t\x22 >/dev/null')

#update the elevation graph
os.system('/usr/bin/rrdtool graph /var/www/elevation_graph.png \
-w 600 -h 120 -a PNG \
--slope-mode \
--start -86400 --end now \
--font DEFAULT:7: \
--title \x22angle in degrees\x22 \
--watermark \x22`date`\x22 \
--vertical-label \x22degrees\x22 \
--right-axis-label \x22 \x22 \
--lower-limit -50 \
--right-axis 1:0 \
--x-grid MINUTE:10:HOUR:1:MINUTE:120:0:%R \
--alt-y-grid --rigid \
DEF:calculated=/tools/sensors/elevation_db.rrd:elev:MAX \
DEF:actual=/tools/sensors/actual_elevation_db.rrd:elev:MAX \
LINE1:calculated\x23707070:\x22Calculated\x22:dashes \
LINE1:actual\x230000FF:\x22Actual\x22 \
GPRINT:calculated:LAST:\x22Cur Calc\: %5.2lf\x22 \
GPRINT:actual:LAST:\x22Cur Actual\: %5.2lf\x22 \
GPRINT:calculated:AVERAGE:\x22Avg\: %5.2lf\x22 \
GPRINT:calculated:MAX:\x22Max\: %5.2lf\x22 \
GPRINT:calculated:MIN:\x22Min\: %5.2lf\t\t\t\x22 >/dev/null')

#Update the solar file for reporting
logsolar = open('/tools/inputs/solarvalues.txt','w')
writeline=("solar_heading: " + str(round((float(getsolarheading())),1)) + "\n")
writeline=("solar_elevation: " + str(round((float(getsolarangle())),1))+ "\n")
print ("Getcurangle: ", getcurangle())
writeline=("actual_elevation: " + str(round((float(getcurangle())),1))+ "\n")
print ("Getcurheading: ", getcurheading())
writeline=("actual_heading: " + str(round((float(getcurheading())),1))+ "\n")

#Create the index.html page
loghtml = open('/var/www/index.html','w')
writeline=("<font size=\x222\x22 face=\x22verdana\x22>\n")
writeline=("<head><meta http-equiv=\x22refresh\x22 content=\x2260\x22></head> \n")
writeline=("<link rel=\x22shortcut icon\x22 href=\x22/favicon.ico\x22/> \n")
writeline=("<link rel=\x22apple-touch-icon\x22 href=\x22apple-icon.png\x22/> \n \
<link rel=\x22apple-touch-icon\x22 sizes=\x2272x72\x22 href=\x22apple-icon-72x72.png\x22/> \n \
<link rel=\x22apple-touch-icon\x22 sizes=\x22114x114\x22 href=\x22apple-icon-114x114.png\x22/> \n \
<link rel=\x22apple-touch-icon\x22 sizes=\x22120x120\x22 href=\x22apple-icon-120x120.png\x22/> \n \
<link rel=\x22apple-touch-icon\x22 sizes=\x22144x144\x22 href=\x22apple-icon-144x144.png\x22/> \n \
<link rel=\x22apple-touch-icon\x22 sizes=\x22152x152\x22 href=\x22apple-icon-152x152.png\x22/> \n \
<link rel=\x22apple-touch-icon\x22 sizes=\x22180x180\x22 href=\x22apple-icon-180x180.png\x22/> \n")
writeline=("<link rel=\x22shortcut icon\x22 type=\x22image/x-icon\x22 href=\x22/favicon.ico\x22/> \n")
writeline=("<title>Solar Robot</title> \n")
writeline =("<font face=\x22helvetica\x22> \n")
writeline =("<link rel=\x22apple-touch-icon\x22 href=/icon_114.png\x22 /> \n")
writeline =("<table style=\x22undefined;table-layout: fixed; width: 387px\x22> \n<colgroup> \n<col style=\x22width: 160px\x22> \n<col style=\x22width: 240px\x22> \n </colgroup> \n")
writeline =("<tr> \n <td><a href=\x22/config/inputs.html\x22>Change Settings</a></td> \n <td><a href=\x22/debug-solar.csv\x22>Debug Log CSV</a></td> \n </tr> \n")
writeline =("<tr> \n <td>Date Time</td> \n <td>" + str( + "</td> \n </tr> \n")
writeline =("<tr> \n <td>Raw Azimuth</td> \n <td>" + str(getrawazimuth()) + "</td> \n </tr> \n <tr> \n <td>Calculated Heading</td> \n <td>" + str(getsolarheading()) + "</td> \n")
writeline =(" </tr> \n <tr> \n <td>Actual Heading</td> \n <td>" + str(getcurheading()) + "</td> \n </tr> \n")
writeline = (" <tr> \n <td>Calculated Angle</td> \n <td>" + str(getsolarangle()) + "</td> \n </tr> \n")
writeline = (" <tr> \n <td>Current Angle</td> \n <td>" + str(getcurangle()) + "</td> \n </tr> \n</table>")
writeline =("<br></br> \n")
# we add the temp graph to the html but generate the image in the other script
writeline =("<img src=\x22internal_temp_graph.png\x22 alt=\x22[internal_temp_graph]\x22><br></br> \n")
writeline =("<img src=\x22azimuth_graph.png\x22 alt=\x22[elevation_graph]\x22><br></br> \n")
writeline =("<img src=\x22elevation_graph.png\x22 alt=\x22[elevation_graph]\x22><br></br></font> \n")
#print (, ",", getrawazimuth(), ",", getsolarheading(), ",", getcurheading(), ",", getsolarangle(),",", getcurangle(),",",tomorrow_static)
print (, ",", getrawazimuth(), ",", getsolarheading(), ",", getcurheading(), ",", getsolarangle(),",", getcurangle())

motors.setSpeeds(0, 0)

it to Python-3.x. i am not a programmer - but the web is big and people are helpful and for Jay's program we go a lot from a friend in Denmark whom we found on our PCLinuxOS forums. We're using a (ITG3200_ITG3205 ADXL345 HMC5883L) IMU and have finally managed to connect it to our Pi B, get a green light and, using Joan's programs (from the Raspberry Pi forums), see that each of the sensors will provide readings to a term.

So now, i think the remaining challenge is how do i get Jay Dosher's program to pull in the readings from the IMU? His program is using what i understand is the Serial method of acquiring data - it has caused great headaches because i didn't realize that it had been written in Python-2.x and when i started "debugging" it in Python-3.x i had lots of problems with bits vs plain text. i don't know if the i2C protocol is easier, but i doubt few things could be more difficult - lol. His program is also written for two motors and our heater is only using one. i suspect we could also do away with all of his Apache stuff since website like ThingSpeak now exist...

Anyway, do you have any ideas as to how to proceed from here?

Have a great morning! :)
User avatar
Posts: 4
Joined: April 26th, 2012, 12:30 pm
Location: Mexicali, Baja California, Mexico

Re: Parabolic solar hot water heater + Pi

Postby bandersnatch » March 20th, 2016, 9:20 pm


I havent the time to analyze the posted code in detail but here are a few tips to help you come to grips with the code

Re-using existing code can be daunting 'cos you dont initially know how it works & how much of the whole system
you need to understand in order to make it do what you want.

My strategy for bending third party code for my own purposes is:

1/ First Get the original code to run
2/ Remove everything you dont need
3/ Add the stuff you do need

1/ You need to start with something that works before removing stuff & adding new stuff
The quickest way is to create an environment as close as possible to the original environment.
- Same OS, same version, same language & version etc., etc.
The problem is that many development tools/libraries are deeply entangled in the operating system
and mangling your existing system to suit is not a good idea.
The easiest solution is to use a virtual machine (VMWare, Oracle VirtualBox etc.)
This allows you to create exactly the same environment, same OS, same Python 2.x, same version of apache etc. etc.
in an encapsulated system without messing up your host machine.
The idea is to get the original code to run with a minimum of effort.
If you dont have the HW then just replace HW calls with hard coded data values (!)
The results are irrelevant, just get the sucker running & dont waste too much time trying to understand how it works

2/ Once it runs, brutally add comments everywhere to chop out the stuff you dont need
hardcode the data returned by the commented functions if necessary
The cycle is : Chop, run & test, chop less or more, run & test
Chop the sucker back to the bare bones but always ending up with a running system
You will automatically learn heaps about the how the system works, without actually trying

3/ Now you can either start experimenting with new code or first port the bare-bones system
to the real target environment
MUCH easier 'cos most of the code is gone
You now automatically know much more about how the program works as a result of the chopping/test work

4/ Adding new code
- Work in small steps & stay "success oriented" by remaining with a "running" system
- Look for other code examples, hardcode the returned values & then replace then with actual results one by one..
- Remove your changes & make even smaller changes if it doesn't work
- Even a tiny change that works & runs OK is better than spending hours trying to figure out why your big change doesn't work

I hope this encourages you to dive in. The main trick is to get the system running & keep it running after each step.
This has a powerful effect on your confidence & motivation.

Good luck
Posts: 150
Joined: September 17th, 2014, 12:06 pm

Return to Help Me! Software

Who is online

Users browsing this forum: No registered users and 1 guest