Created
October 13, 2013 03:59
-
-
Save lanyonm/6958018 to your computer and use it in GitHub Desktop.
The python script used to collect XBee Kill-a-Watt data.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import serial, time, datetime, sys | |
from xbee import xbee | |
from socket import socket | |
import sensorhistory | |
LOGFILENAME = "powerdatalog.csv" # where we will store our flatfile data | |
DEFAULT_CARBON_SERVER = 'localhost' | |
DEFAULT_CARBON_PORT = 2003 | |
SERIALPORT = "/dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGADQ32-if00-port0" # the com/serial port the XBee is connected to | |
BAUDRATE = 9600 # the baud rate we talk to the xbee | |
CURRENTSENSE = 4 # which XBee ADC has current draw data | |
VOLTSENSE = 0 # which XBee ADC has mains voltage data | |
MAINSVPP = 170 * 2 # +-170V is what 120Vrms ends up being (= 120*2sqrt(2)) | |
vrefcalibration = [492, # Calibration for sensor #0 | |
491, # Calibration for sensor #1 | |
489, # Calibration for sensor #2 | |
492, # Calibration for sensor #3 | |
501, # Calibration for sensor #4 | |
493] # etc... approx ((2.4v * (10Ko/14.7Ko)) / 3 | |
CURRENTNORM = 15.5 # conversion to amperes from ADC | |
NUMWATTDATASAMPLES = 1800 # how many samples to watch in the plot window, 1 hr @ 2s samples | |
# open up the FTDI serial port to get data transmitted to xbee | |
ser = serial.Serial(SERIALPORT, BAUDRATE) | |
try: | |
ser.open() | |
except: | |
print "%s was already open!" % (SERIALPORT) | |
# open our datalogging file | |
logfile = None | |
try: | |
logfile = open(LOGFILENAME, 'r+') | |
except IOError: | |
# didn't exist yet | |
logfile = open(LOGFILENAME, 'w+') | |
logfile.write("#Date, time, sensornum, avgWatts\n"); | |
logfile.flush() | |
DEBUG = False | |
if (sys.argv and len(sys.argv) > 1): | |
if sys.argv[1] == "-d": | |
DEBUG = True | |
sensorhistories = sensorhistory.SensorHistories(logfile) | |
print sensorhistories | |
# the 'main loop' runs once a second or so | |
def update_graph(idleevent): | |
global avgwattdataidx, sensorhistories, DEBUG | |
# grab one packet from the xbee, or timeout | |
packet = xbee.find_packet(ser) | |
if not packet: | |
return # we timedout | |
xb = xbee(packet) # parse the packet | |
#print xb.address_16 | |
if DEBUG: # for debugging sometimes we only want one | |
print xb | |
# we'll only store n-1 samples since the first one is usually messed up | |
voltagedata = [-1] * (len(xb.analog_samples) - 1) | |
ampdata = [-1] * (len(xb.analog_samples ) -1) | |
# grab 1 thru n of the ADC readings, referencing the ADC constants | |
# and store them in nice little arrays | |
for i in range(len(voltagedata)): | |
voltagedata[i] = xb.analog_samples[i+1][VOLTSENSE] | |
ampdata[i] = xb.analog_samples[i+1][CURRENTSENSE] | |
if DEBUG: | |
print "ampdata: "+str(ampdata) | |
print "voltdata: "+str(voltagedata) | |
# get max and min voltage and normalize the curve to '0' | |
# to make the graph 'AC coupled' / signed | |
min_v = 1024 # XBee ADC is 10 bits, so max value is 1023 | |
max_v = 0 | |
for i in range(len(voltagedata)): | |
if (min_v > voltagedata[i]): | |
min_v = voltagedata[i] | |
if (max_v < voltagedata[i]): | |
max_v = voltagedata[i] | |
# figure out the 'average' of the max and min readings | |
avgv = (max_v + min_v) / 2 | |
# also calculate the peak to peak measurements | |
vpp = max_v-min_v | |
for i in range(len(voltagedata)): | |
#remove 'dc bias', which we call the average read | |
voltagedata[i] -= avgv | |
# We know that the mains voltage is 120Vrms = +-170Vpp | |
voltagedata[i] = (voltagedata[i] * MAINSVPP) / vpp | |
# normalize current readings to amperes | |
for i in range(len(ampdata)): | |
# VREF is the hardcoded 'DC bias' value, its | |
# about 492 but would be nice if we could somehow | |
# get this data once in a while maybe using xbeeAPI | |
if vrefcalibration[xb.address_16]: | |
ampdata[i] -= vrefcalibration[xb.address_16] | |
else: | |
ampdata[i] -= vrefcalibration[0] | |
# the CURRENTNORM is our normalizing constant | |
# that converts the ADC reading to Amperes | |
ampdata[i] /= CURRENTNORM | |
#print "Voltage, in volts: ", voltagedata | |
#print "Current, in amps: ", ampdata | |
# calculate instant. watts, by multiplying V*I for each sample point | |
wattdata = [0] * len(voltagedata) | |
for i in range(len(wattdata)): | |
wattdata[i] = voltagedata[i] * ampdata[i] | |
# sum up the current drawn over one 1/60hz cycle | |
avgamp = 0 | |
# 16.6 samples per second, one cycle = ~17 samples | |
# close enough for govt work :( | |
for i in range(17): | |
avgamp += abs(ampdata[i]) | |
avgamp /= 17.0 | |
# sum up power drawn over one 1/60hz cycle | |
avgwatt = 0 | |
# 16.6 samples per second, one cycle = ~17 samples | |
for i in range(17): | |
avgwatt += abs(wattdata[i]) | |
avgwatt /= 17.0 | |
# Print out our most recent measurements | |
print str(xb.address_16)+"\tCurrent draw, in amperes: "+str(avgamp) | |
print "\tWatt draw, in VA: "+str(avgwatt) | |
if (avgamp > 13): | |
return # hmm, bad data | |
# retreive the history for this sensor | |
sensorhistory = sensorhistories.find(xb.address_16) | |
# add up the delta-watthr used since last reading | |
# Figure out how many watt hours were used since last reading | |
elapsedseconds = time.time() - sensorhistory.lasttime | |
dwatthr = (avgwatt * elapsedseconds) / (60.0 * 60.0) # 60 seconds in 60 minutes = 1 hr | |
sensorhistory.lasttime = time.time() | |
print "\t\tWh used in last ",elapsedseconds," seconds: ",dwatthr | |
sensorhistory.addwatthr(dwatthr) | |
# Determine the minute of the hour (ie 6:42 -> '42') | |
currminute = (int(time.time())/60) % 10 | |
# Figure out if its been five minutes since our last save | |
if (((time.time() - sensorhistory.fiveminutetimer) >= 60.0) | |
and (currminute % 5 == 0) | |
): | |
# Print out debug data, Wh used in last 5 minutes | |
avgwattsused = sensorhistory.avgwattover5min() | |
print time.strftime("%Y %m %d, %H:%M")+", "+str(sensorhistory.sensornum)+", "+str(sensorhistory.avgwattover5min())+"\n" | |
# Lets log it! Seek to the end of our log file | |
if logfile: | |
logfile.seek(0, 2) # 2 == SEEK_END. ie, go to the end of the file | |
logfile.write(time.strftime("%Y %m %d, %H:%M")+", "+ | |
str(sensorhistory.sensornum)+", "+ | |
str(sensorhistory.avgwattover5min())+"\n") | |
logfile.flush() | |
sock = socket() | |
try: | |
sock.connect( (DEFAULT_CARBON_SERVER, DEFAULT_CARBON_PORT) ) | |
except: | |
print "WARNING: Couldn't connect to %(server)s on port %(port)d, is graphite running?" % { 'server':server, 'port':port } | |
timestamp = int( time.time() ) | |
if (sensorhistory.sensornum == 1): | |
message = "%s %s %s" % ("wattage.office.watts", sensorhistory.avgwattover5min(), int(time.time())) | |
sock.sendall(message + "\n") | |
sock.close() | |
# Reset our 5 minute timer | |
sensorhistory.reset5mintimer() | |
while True: | |
update_graph(None) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment