Files
ferrarismeter/kwhmeter_20131014.py
2025-06-23 11:51:54 +02:00

555 lines
19 KiB
Python

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