Tuesday 28 March 2017

Sky Capture

Using a standard Raspberry Pi camera 24hr elapsed time video can be generated. Basically, images are captured to a USB memory stick then the excellent Windows PC based ImagesToVideo program from www.cze.cz  reads the memory stick data to form a video.

In addition, individual images can be uploaded using WinSCP so interesting results flagged by the skymeter program can be investigated by examining the matching images. All this using a laptop from the comfort of an armchair !

To form a video the USB stick has to be retrieved, prefereable around the switchover times so as not to loose too many images from the next sequence. After reinserting the memory stick, the Pi is rebooted and if the auto start time is missed the relevant startup program can be intiated remotely using Putty.

Various cameras fields were tried. The fisheye (90deg x 104deg) gave good results but need to be mounted pointing upward with implications for weather protection. Tests using a sheet of  glass to keep the rain off led to problems of dirt and internal reflections. So the standard camera  (40deg x 55deg) has been settled on as it has a narrower FOV and can be mounted under a protective roof overhang. It points due south and captures the ecliptic and having a bit of foreground imagary produces more interesting videos anyway. It is supported by a simple bracket as shown and powered off a mains USB adaptor. The Pi 3 is used which has integral WiFi.




The Python software is straightforward and is reproduced below. It is invoked with three command line parameters:-
- number of takes required
- interval in seconds between takes
- exposure time in uSec

To select different paramters for day and night, the crontab facility is used:-
0 6 * * * ./daystartup.sh
0 20 * * * ./nightstartup.sh
@reboot ./mystartup.sh &

############ daystartup.sh ##############
sudo killall python
python allsky.py 2400 20 500

############ nightstartup #############
sudo killall python
python allsky.py 2400 1 6000000

########### mystartup.sh ##############
sudo mount /dev/sda1 /media/usb2 -o uid=pi,gid=pi
 
Each day/night startup kills the previous  version of allsky.py then starts it again using different command line values.  A new directory is created by allsky.py at the start of each image sequence and images are automatically numbered in sequence.
The mystartup.sh is invoked after power-up and mounts the USB stick on directory /media/usb2. The media directory is the one two levels back from the /home/pi directory, it is NOT the media directory visible at the /home/pi level.  
Note that the night time 6Sec exposure actually results in an image take time of about 25Secs so a delay of 1Sec is appropriate, giving about two exposures a minute.

##################### allsky.py ################################
# Using a Raspberry Pi camera to generate elapsed time video
# using ImagesToVideo from  www.cze.cz
#
# Geo Meadows
# 22Mar2017 - first issue
#
##############################################################
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 sys
import argparse

def takeStills(exp,n,ival):
    with picamera.PiCamera() as camera:
        camera.resolution = (1296,972)    #mode 4
        if (exp > 1000000):
           camera.framerate=Fraction(1, 6)
        camera.exposure_mode = 'off'
        camera.shutter_speed = exp
        print "Exp time = {:.6f} Secs".format(float(exp)/1000000)
        camera.iso = 400
        camera.start_preview()
        sleep(1)
        for i, filename in enumerate(camera.capture_continuous('{counter:04d}.jpeg')):
   #        camera.annotate_background = picamera.Color('black')
            camera.annotate_text = dt.datetime.now().strftime('%y-%m-%d %H:%M.%S')
            print('Captured image %s' % filename)
            sleep(ival)
            if i == n-1:
                break
        camera.stop_preview()
        
def cleanAndExit():
    print "Ended!"
    sys.exit()

################# Main ###################
parser = argparse.ArgumentParser()
parser.add_argument("takes", type=int)
parser.add_argument("delay", type=int)
parser.add_argument("exptime", type=int)
args = parser.parse_args()
print args.takes, ' takes'
print args.delay, ' Secs delay'
print args.exptime, ' uS exp time'
takes = args.takes
delay = args.delay
exptime = args.exptime

today=time.strftime("%d%b-%H.%M-")
print "Starting %d frames -%s " % (takes, today)
os.chdir('/media/usb2/')
if not os.path.exists(today):
    os.makedirs(today)
os.chdir(today)
try:
   takeStills(exptime,takes,delay)         
except (KeyboardInterrupt, SystemExit):
  cleanAndExit()
cleanAndExit()



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()