# This Python script needs a SPI module. # For more info, please see: # http://www.100randomtasks.com/simple-spi-on-raspberry-pi # # This script also uses a "LoadAverage" module. This module can be downloaded from: # http://patrick.i234.me/kwhmeter/LoadAverage.py # # This script is written for use on a Raspberri Pi, in combination with some # custom-made hardware. # The schematic of the hardware can be found here: # https://www.circuitlab.com/circuit/eae95u/raspberry-pi-kwh-meter/ # # or an PDF can be found on my own site: # http://patrick.i234.me/kwhmeter/raspberry-pi-kwh-meter.pdf # # # # Unused modules, that I might incorporate later #import rrdtool #import os import MySQLdb import spidev import time import RPi.GPIO as GPIO import ConfigParser # for the module LoadAverage, see: http://patrick.i234.me/kwhmeter/LoadAverage.py import LoadAverage import datetime from datetime import date # For PVoutput import urllib, urllib2 DEBUG = False # GPIO Pins for the different components: switch = 23 redLED = 17 greenLED = 18 boardLED = 22 # Initialise SPI spi = spidev.SpiDev() spi.open(0, 0) # read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7) def readadc(adcnum): if ((adcnum > 7) or (adcnum < 0)): return -1 r = spi.xfer2([1, (8 + adcnum) << 4, 0]) adcout = ((r[1] & 3) << 8) + r[2] return adcout def turnonLED(LED): GPIO.output(LED, True) def turnoffLED(LED): GPIO.output(LED, False) def toggleLED(LED): GPIO.output(LED, GPIO.input(LED) ^ 1) def writeLog(watt, total, importCount, exportCount, rotTime, loadaverage): localtime = time.localtime(time.time()) filename = '%s_kWh-meter.log' % time.strftime("%Y-%m-%d", localtime) line = "%s\t%s\t%s\t%s\t%.3f\t%.3f\t%.3f\t%.0f\t%.3f\t%.3f\t%.3f\n" % ( time.strftime("%Y-%m-%d %H:%M:00", localtime), time.strftime("%Y-%m-%d", localtime), time.strftime("%H:%M:00", localtime), watt, total, importCount, exportCount, rotTime, loadaverage[0], loadaverage[1], loadaverage[2]) logfile = open(filename, 'a') try: logfile.write(line) finally: logfile.close() logfile.close() def writesettings(config): with open('kwhmeter.conf', 'wb') as configfile: config.write(configfile) return True def readsettings(): config = ConfigParser.SafeConfigParser() config.read('kwhmeter.conf') if not config.has_section('kWh Settings'): config.add_section('kWh Settings') if not config.has_option('kWh Settings', 'LeftChannelID'): config.set('kWh Settings', 'LeftChannelID', '2') if not config.has_option('kWh Settings', 'RightChannelID'): config.set('kWh Settings', 'RightChannelID', '0') # Section kWh data if not config.has_section('kWh'): config.add_section('kWh') if not config.has_option('kWh', 'importCounter'): config.set('kWh', 'importCounter', '0') if not config.has_option('kWh', 'exportCounter'): config.set('kWh', 'exportCounter', '0') if not config.has_option('kWh', 'rotationsPerKWh'): config.set('kWh', 'rotationsPerKWh', '600') if not config.has_option('kWh', 'cumuliday'): config.set('kWh', 'cumuliday', date.today().strftime("%Y%m%d")) # Add Pvoutput stuff if not config.has_section('pvout'): config.add_section('pvout') if not config.has_option('pvout','pvout_enabled'): config.set('pvout','pvout_enabled','false') if not config.has_option('pvout','pvout_apikey'): config.set('pvout','pvout_apikey','0') if not config.has_option('pvout','pvout_sysid'): config.set('pvout','pvout_sysid','0') # If you want to save in a MySQL DB, uncomment below: # Section MySQL #if not config.has_section('MySQL'): # config.add_section('MySQL') #if not config.has_option('MySQL', 'host'): # config.set('MySQL', 'host', 'localhost') #if not config.has_option('MySQL', 'user'): # config.set('MySQL', 'user', 'root') #if not config.has_option('MySQL', 'passwd'): # config.set('MySQL', 'passwd', 'root') #if not config.has_option('MySQL', 'db'): # config.set('MySQL', 'db', 'kwhRijnsraat214') return config def savekwhdata(config, importCounter, exportCounter): config.set('kWh', 'importCounter', "%.0f" % importCounter) config.set('kWh', 'exportCounter', "%.0f" % exportCounter) writesettings(config) # If you want to save in a MySQL DB, uncomment below: #def savetodb(config, watt, total, imported, exported): # section = 'MySQL' # localtime = time.localtime(time.time()) # # conn = MySQLdb.connect(host = config.get(section, 'host'), # user = config.get(section, 'user'), # passwd = config.get(section, 'passwd'), # db = config.get(section, 'db')) # cursor = conn.cursor() # # try: # cursor.execute( # "INSERT INTO `DayDatakWh` (`DateTime`, `CurrentPower`, `ETotalToday`, `EImportToday`, `EExportToday`, `CurrentUsage`, `EUsageToday`, `PVOutput`, `CHANGETIME`) VALUES ('%s', %s, %.3f, %.3f, %.3f, NULL, NULL, NULL, '0000-00-00 00:00:00');" % ( # time.strftime("%Y-%m-%d %H:%M:00", localtime), watt, total, imported, exported)) # conn.commit() # except: # conn.rollback() # conn.close() # # return True def printinfo(line): timeStr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) print "%s\t%s" % (timeStr, line) #serialDev.writeLine("%s\t%s" % (timeStr, line)) def exportPvoutput(config, gentotal, genpower, contotal, conpower): pvout_enabled = config.getboolean('pvout','pvout_enabled') pvout_apikey = config.get('pvout','pvout_apikey') pvout_sysid = config.get('pvout','pvout_sysid') url = "http://pvoutput.org/service/r2/addstatus.jsp" now = datetime.datetime.now() get_data = { 'key': pvout_apikey, 'sid': pvout_sysid, 'd': now.strftime('%Y%m%d'), 't': now.strftime('%H:%M'), 'v1': gentotal * 1000, 'v2': genpower, 'v3': contotal * 1000, 'v4': conpower, 'c1': "1" } get_data_encoded = urllib.urlencode(get_data) # UrlEncode the parameters printinfo(get_data_encoded) request_object = urllib2.Request(url + '?' + get_data_encoded) # Create request object try: response = urllib2.urlopen(request_object) #make the request and store the response printinfo("Pvoutput Exported") return True except urllib.error.HTTPError, e: printinfo('HTTPError = ' + str(e.code)) return False except Exception: import traceback checksLogger.error('generic exception: ' + traceback.format_exc()) #http://pvoutput.org/service/r2/addstatus.jsp? def calibratesensors(config): printinfo("Do calibration of sensors . . .") sensorleftLValue = 9999 sensorleftHValue = -1 sensorrightLValue = 9999 sensorrightHValue = -1 turnoffLED(boardLED) turnoffLED(greenLED) turnonLED(redLED) startTime = millis() elapsedTime = millis() - startTime startBlink = millis() startInfo = millis() sensorleftDiff = 0 sensorrightDiff = 0 while (elapsedTime < 30000) or (sensorleftDiff < 40) or (sensorrightDiff < 40): #or not GPIO.input(switch): # run calibration for min. 10 seconds, # and distance between low and high value must be at least 40 ticks # or while the switch on the board is hold down (pressing the switch = 0, not pressing the switch = 1) sensordata = samplesensors(config) sensorleftCurrent = sensordata['left'] sensorrightCurrent = sensordata['right'] if sensorleftLValue > sensorleftCurrent: sensorleftLValue = sensorleftCurrent if sensorleftHValue < sensorleftCurrent: sensorleftHValue = sensorleftCurrent if sensorrightLValue > sensorrightCurrent: sensorrightLValue = sensorrightCurrent if sensorrightHValue < sensorrightCurrent: sensorrightHValue = sensorrightCurrent if DEBUG: printinfo("Sensor left: %s\tSensor right: %s" % (sensorleftCurrent, sensorrightCurrent)) if millis() - startBlink > 100: toggleLED(boardLED) toggleLED(greenLED) toggleLED(redLED) startBlink = millis() sensorleftDiff = sensorleftHValue - sensorleftLValue sensorrightDiff = sensorrightHValue - sensorrightLValue if millis() - startInfo > 1000: printinfo("Time %s: \tSL: %s (diff: %s) \t\tSR: %s (diff: %s)" % (elapsedTime, sensorleftCurrent, sensorleftDiff, sensorrightCurrent, sensorrightDiff)) startInfo = millis() time.sleep(0.1) elapsedTime = millis() - startTime sensorleftLTh = sensorleftLValue + ((sensorleftDiff / 10) * 5) # 50% above min sensorleftHTh = sensorleftHValue - ((sensorleftDiff / 10) * 2) # 20% below max sensorrightLTh = sensorrightLValue + ((sensorrightDiff / 10) * 5) # 50% above min sensorrightHTh = sensorrightHValue - ((sensorrightDiff / 10) * 2) # 20% below max turnoffLED(boardLED) turnoffLED(greenLED) turnoffLED(redLED) printinfo("Calibration done!") printinfo("Left low: %s" % sensorleftLValue) printinfo(" high: %s" % sensorleftHValue) printinfo(" diff: %s" % sensorleftDiff) printinfo(" lthld.%s" % sensorleftLTh) printinfo(" hthld.%s" % sensorleftHTh) printinfo("Right low: %s" % sensorrightLValue) printinfo(" high: %s" % sensorrightHValue) printinfo(" diff: %s" % sensorrightDiff) printinfo(" lthld.%s" % sensorrightLTh) printinfo(" hthld.%s" % sensorrightHTh) printinfo("") config.set('kWh', 'minleft', "%.0f" % sensorleftLValue) config.set('kWh', 'maxleft', "%.0f" % sensorleftHValue) config.set('kWh', 'minmarginleft', "%.0f" % sensorleftLTh) config.set('kWh', 'maxmarginleft', "%.0f" % sensorleftHTh) config.set('kWh', 'minright', "%.0f" % sensorrightLValue) config.set('kWh', 'maxright', "%.0f" % sensorrightHValue) config.set('kWh', 'minmarginright', "%.0f" % sensorrightLTh) config.set('kWh', 'maxmarginright', "%.0f" % sensorrightHTh) return config def millis(): return int(round(time.time() * 1000)) def samplesensors(config): # Sample the left and right sensor for maxsamples times (in order to get an average value) leftid = config.getint('kWh Settings', 'LeftChannelID') rightid = config.getint('kWh Settings', 'RightChannelID') maxsamples = 20 sensorleft = 0 sensorright = 0 for loopCount in range(0, maxsamples): sensorleft += readadc(leftid) sensorright += readadc(rightid) #print "%s | %s" % (sensorleft, sensorright) sensorleft /= maxsamples sensorright /= maxsamples return {'left': sensorleft, 'right': sensorright} def dotFollower(config): # This is the main part of the code, which will start to minitor the black dot on the disc # Do some initialisations rotationsperkwh = config.getfloat("kWh", "rotationsPerKWh") * 1.00 counterimport = config.getint("kWh", "importCounter") counterexport = config.getint("kWh", "exportCounter") countertotal = counterimport + counterexport currentpower = 0 timeperrotation = 0 left = False right = False leftdetected = False rightdetected = False lefttimestamp = time.time() leftprevtimestamp = time.time() righttimestamp = time.time() rightprevtimestamp = time.time() sensorinnertimestamp = time.time() sensorinnerlength = 0 sensoroutertimestamp = time.time() sensoroutherlength = 0 exportpower = 0 importpower = 0 thisday = config.get("kWh", "cumuliday") printinfo("Date: %s" % thisday) leftmin = config.getint("kWh", "minmarginleft") leftmax = config.getint("kWh", "maxmarginleft") rightmin = config.getint("kWh", "minmarginright") rightmax = config.getint("kWh", "maxmarginright") logexported = False loadaverage = LoadAverage.Loadaverage() loadaverageadded = False # Keep on running while the switch is NOT pressed while 1: #GPIO.input(switch): sensordata = samplesensors(config) # left sensor if sensordata['left'] >= leftmax and not left: # turn left to False left = True turnonLED(greenLED) leftdetected = True leftprevtimestamp = lefttimestamp lefttimestamp = time.time() #printinfo("Left sensor: . %.5f secs" % (lefttimestamp - leftprevtimestamp)) if not rightdetected: sensorinnertimestamp = time.time() sensoroutherlength = time.time() - sensoroutertimestamp else: sensoroutertimestamp = time.time() sensorinnerlength = time.time() - sensorinnertimestamp if sensordata['left'] <= leftmin and left: # turn left to False left = False turnoffLED(greenLED) #printinfo("Left off") # right sensor if sensordata['right'] >= rightmax and not right: # turn right to False right = True turnonLED(redLED) rightdetected = True rightprevtimestamp = righttimestamp righttimestamp = time.time() #printinfo("Right sensor:. %.5f secs" % (righttimestamp - rightprevtimestamp)) if not leftdetected: sensorinnertimestamp = time.time() sensoroutherlength = time.time() - sensoroutertimestamp else: sensoroutertimestamp = time.time() sensorinnerlength = time.time() - sensorinnertimestamp if sensordata['right'] <= rightmin and right: # turn right to False right = False turnoffLED(redLED) #printinfo("Right off") if not left and not right and leftdetected and rightdetected: #rotation detected! leftdetected = False rightdetected = False if sensorinnerlength < sensoroutherlength: # detection of left and right was correct turnonLED(boardLED) # Determine direction if lefttimestamp < righttimestamp: # The left sensor was seen before the right # S1 --> S2 # Rotation is "importing from the net" rotation = 1 counterimport += 1 else: # The right sensor was seen before the left # S1 <-- S2 # Rotation is "exporting to the net" rotation = -1 counterexport += 1 countertotal = counterimport - counterexport # I'll average the left and right rotation time to get a "smoother" value (in principle I calculate the point between the 2 sensors) timeperrotation = (((lefttimestamp - leftprevtimestamp) + (righttimestamp - rightprevtimestamp)) / 2) currentpower = int(rotation * rotationsperkwh * 10 / (timeperrotation)) if rotation == 1: printinfo("-> importing from net") importpower = currentpower exportpower = 0 else: printinfo("<- exporting to net") exportpower = currentpower * -1 importpower = 0 #printinfo("Rotation time: . %.5f secs" % timeperrotation) #printinfo("-- -- -- -- -- -- -- -- -- -- --") #printinfo("Inner gab: . . . %.5f secs" % sensorinnerlength) #printinfo("Outer: . . . . . %.5f secs" % sensoroutherlength) #printinfo("Total: . . . . . %.5f secs" % (sensoroutherlength + sensorinnerlength)) printinfo("Rotation time: . %.5f secs" % timeperrotation) printinfo("Current power: . %.0f Watt" % currentpower) #printinfo("ImportCount: . . %.0f" % counterimport) #printinfo("ExportCount: . . %.0f" % counterexport) printinfo("Import:. . . . . %.2f kWh" % (counterimport / rotationsperkwh)) printinfo("Export:. . . . . %.2f kWh" % (counterexport / rotationsperkwh)) printinfo("Total: . . . . . %.2f kWh" % (countertotal / rotationsperkwh)) printinfo("Load average:. . %.0f W, %.0f W, %.0f W" % loadaverage.loadaverage()) printinfo("--------------------------------------------------------------------------------") # do a tiny nap time.sleep(0.1) turnoffLED(boardLED) else: # Ooops... inner gab is bigger then the outer route??? We are detecting the wrong way! # sleep a bit and try again printinfo("Skipping rotation . . .") if (sensoroutherlength / 4) < 10: time.sleep(sensoroutherlength / 4) else: time.sleep(10) else: time.sleep(0.001) if time.gmtime()[5] % 10 == 0: # Every 10 seconds print the current values if not loadaverageadded: loadaverage.addvalue(currentpower) loadaverageadded = True if time.gmtime()[4] % 5 == 0: # Every 5 minutes, write a log entry & DB record if not logexported: writeLog(currentpower, (countertotal / rotationsperkwh), (counterimport / rotationsperkwh), (counterexport / rotationsperkwh), (timeperrotation * 1000), loadaverage.loadaverage()) savekwhdata(config, counterimport, counterexport) # If you want to save to pvoutput, uncomment the line below: exportPvoutput(config, (counterexport / rotationsperkwh), exportpower, (counterimport / rotationsperkwh), importpower) logexported = True else: logexported = False else: loadaverageadded = False if thisday != date.today().strftime("%Y%m%d"): if logexported == True: exportpower = 0 importpower = 0 thisday = date.today().strftime("%Y%m%d") config.set('kWh', 'cumuliday',thisday) writesettings(config) # Main program routine begins here: #---------------------------------- config = readsettings() writesettings(config) #init raspberry pi gpio pins GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) GPIO.setup(switch, GPIO.IN) GPIO.setup(boardLED, GPIO.OUT, initial = GPIO.LOW) GPIO.setup(greenLED, GPIO.OUT, initial = GPIO.LOW) GPIO.setup(redLED, GPIO.OUT, initial = GPIO.LOW) #set LEDs default off turnoffLED(boardLED) turnoffLED(greenLED) turnoffLED(redLED) writesettings(config) # Infinite loop while True: config = calibratesensors(config) dotFollower(config) # we will never get here... GPIO.remove_event_detect(switch) GPIO.cleanup()