Skip to content

Instantly share code, notes, and snippets.

@lanyonm
Created October 13, 2013 03:59
Show Gist options
  • Save lanyonm/6958018 to your computer and use it in GitHub Desktop.
Save lanyonm/6958018 to your computer and use it in GitHub Desktop.
The python script used to collect XBee Kill-a-Watt data.
#!/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