Thursday 2 March 2017

Sky Meter

This blog describes a simple application of the Raspberry Pi Camera, a light meter to record day and night outdoor light levels. It is supplemented by a wide angle camera (90 x 104 degrees) to capture sky images for processing into videos. See the separtate blog entry for this.

The meter is a straightforward Rapberry Pi with camera and standard lens (40x55 degree FOV), linked by WiFi to the internet where reading are transfered every 4 minutes to Thingspeak, which is configured to produce three graphs:
-last six hours using all readings
- the last 24 hrs with averaged reading
-the last 24hrs with the y-axis expanded to better show night levels.

The charts are viewable real time at:-
 https://thingspeak.com/channels/234010

In addition, dynamic and historical data can be displayed, courtesy of the Highcharts data configuration utility, at my web site page:-
http://www.mecol.co.uk/P20.php
By using the width controls at bottom right, it is possible to view details from any time period.

The camera has two exposures, 74/1000Sec for daytime and 10 summed 6 Sec exposures at night, ie. equivalent to a 60 second exposure, about 1,000 times the day value, with automatic changeover at dusk/dawn.  The light level output is not calibrated to any standard, simply scaled as 0 to 100%. Curves show overshoots at dawn/dusk as the exposure times change over.

The light level is determined by calculating the average of the three RGB values of all of the 5Meg pixels.


Clear moonless night with cloud from 2am, then a dull day with heavy rain at 4pm, then a cloudy rainy night

Using the meter itself to determine when dusk/dawn occured was fraught with issues (a full moon being one of them) so after trying various methods I've finally settled on using an astronomical software package called Ephem. I tell this where I live and it computes sunrise & sunset throughout the year. I add an hour each side to stretch the day by two hours to accommodate dayglow effects and it works fine.

I've also added a temperature sensor for air temperature, mainly so that I can use it on other Pi projects that have run out of analogue inputs! They can get the current value from this Thingspeak channel. More information on using the HX711 a/d for this can be found on my HoneyPi blog.

Here's the Python software - feel free to use in whatever way you wish.
##############################################################
# Measure average light level, send to Thingspeak
# and use it to set exposure time for subsequent stills
#
# 2017 - Geo Meadows
#27Feb - initial idea from https://github.com/pageauc/pi-timolo
#28Feb - Long exp added for night time
#           - deadband added to day/night changeovers
#07Mar - timer added to day/night changeovers
#10Mar - changes for move to std piCamera on a Zero Pi
#05Apr - LM35 temperature probe via HX711 added
#12Apr - gain switch changeover now at preset times
#15Apr - use pyephem to detect day time changeover
#
# sudo apt-get install python-dev
# sudo pip install pyephem
##############################################################

from timeit import default_timer as timer
from time import sleep
import os
import picamera
import picamera.array
import subprocess
import time
from fractions import Fraction
import numpy as np
import urllib
import urllib2
import sys
from hx711 import HX711
import RPi.GPIO as GPIO
import ephem

BST = 1                    #set for summer time
                      
THINGSPEAKKEY = 'XXXXXXXXXXXXXXXX'
THINGSPEAKURL = 'https://api.thingspeak.com/update'

def getAvLight(exptime):
   with picamera.PiCamera() as camera:
          with picamera.array.PiRGBArray(camera) as stream:
             camera.resolution = (736, 480)
             #camera.resolution = (2592,1952)
             camera.framerate=Fraction(1, 6)
             camera.awb_mode = 'auto'
             camera.exposure_mode = 'off'
             camera.iso = 800           
             camera.shutter_speed = exptime
             print "Exp time = {:.3f} Secs".format(float(exptime)/1000000)
             camera.start_preview()
             time.sleep(1)
             camera.capture(stream, format='rgb')
             alight = float(np.average(stream.array[...,1]))
             print "This raw level = {:.1f} ".format(alight)
             camera.stop_preview()
             return(alight)  
       
def cleanAndExit():
    print "Bye!"
    sys.exit()

def sendData(url,key,field1,field2,field3,field4,lt1,lt2,lt3,amb):        # Send data to Thingspeak
  values = {'api_key':key, 'field1':lt1, 'field2':lt2, 'field3':lt3, 'field4':amb}
  postdata = urllib.urlencode(values)
  req = urllib2.Request(url, postdata)
  log = time.strftime("%d-%m-%Y,%H:%M:%S") + ","
  log = log + "{:.1f}".format(lt1) + ","
  log = log + "{:.1f}".format(lt2) + ","
  log = log + "{:.1f}".format(lt3) + ","
  log = log + "{:.1f}".format(amb) + ","
  try:                                  
    response = urllib2.urlopen(req, None, 6)
    html_string = response.read()
    response.close()
    log = log + 'Update ' + html_string
  except urllib2.HTTPError, e:
    log = log + 'Server error'
  except urllib2.URLError, e:
    log = log + 'url error'
  except:
    log = log + 'unknown error'
  print log

################# Main ###################

f = open("zero.txt")
chAoffset = f.read()
print "Loading Temp Offset: {}\n".format(chAoffset)
f.close

home = ephem.Observer()
home.lat = '52.4'               # <==  location
home.lon = '-2.17'             # <==       
home.elevation = 212
sun = ephem.Sun()

while True:
   try:
      hx = HX711(17,27,128)        #(data,clk,chan A) Outside Temperature
      hx.set_reading_format("LSB", "MSB")
      hx.set_reference_unit(38000)      #scaling - inc for smaller temp
      hx.set_offset(int(chAoffset))        #set zero offset
      tmp = hx.get_avg_weight(100,5)      #(samples, spikes)
      print "temp = {:.2f}".format(tmp)
      GPIO.cleanup()
    
      r1 = home.next_rising(sun)
      year, month, day, hour, minute, second = r1.tuple()
      risemin = (BST + hour)*60 + minute

      s1 = home.next_setting(sun)
      year, month, day, hour, minute, second = s1.tuple()
      setmin = (BST + hour)*60 + minute

      now = ephem.now()
      hr = BST + now.tuple()[3]
      min = now.tuple()[4]
      nowmin = hr*60 + min
 
      if  (((nowmin+60) > risemin) and (nowmin < (setmin+60))):    #add 60mins dayglow correction
          Daytime = 1
    exptime = 73600
      else:
          Daytime = 0
        exptime = 6000000
 
      print ("Mins from Sunrise = %i" % (nowmin-risemin))
      print ("Mins to Sunset = %i" % (setmin-nowmin))
      print ("Daytime = %i" %Daytime)

      n = 8
      light = 0
      for i in range (0,n):
         light = light + getAvLight(exptime)

      if (Daytime == 1):
         light = light*100/(255*n)
      if (light >99):
         light = 99
      if (light < 0.01 ):
         light = 0.01
      print "Summed Av Light Level ={:.2f} ".format(light)
      sendData(THINGSPEAKURL,THINGSPEAKKEY,'field1','field2','field3','field4',light,light,light,tmp)               
      sys.stdout.flush()
     
      sleep (1)       
   except (KeyboardInterrupt, SystemExit):
      cleanAndExit()

No comments:

Post a Comment