#!/usr/bin/python
"""GPIO Bar Graph
Written by Jonathan A. Foster <jon@jfpossibilities.com>
Started October 30th, 2017
"Unlicensed" (https://unlicense.org) - Free to use however you wish

*NOTE* for code simplicity all bit values *MUST* be strings. No checks are made
and the code will most likely behave poorly if they aren't.
"""
### CONSTANTS ###

GPIOP = '/sys/class/gpio' # Path to GPIO control folder



### LED interface ###

class XIO(object):
	"""Stupid simple output interface to the I/O eXpander

	*NOTE* If close() is not specifically called, this class will leave the
	bits however they were last set. I can think of some uses for that.
	"""
	DEFAULT = '1' # The desired default state of bits

	def __init__(self):
		"""Find base of I/O expander (8574)"""
		self.base = 0
		self.clear()
		from os import listdir
		for x in listdir(GPIOP):
			if x.startswith('gpiochip'):
				x = GPIOP + '/' + x
				if open(x+'/label', 'rt').read().strip() == "pcf8574a":
					self.base = int(open(x+'/base', 'rt').read().strip())
					self.chip = x
					return
		raise IOError('Unable to locate I/O expander')

	def __str__(self):
		return ''.join(self.bits)

	def clear(self):
		"""Clear all bits & io, similar to close, without using setall()"""
		self.io = None
		self.bits = ['', '', '', '', '', '', '', '']

	def open(self):
		"""Open all "bits\""""
		self.clear()
		self.io = [open(GPIOP+'/gpio%d/value' % x, 'wt') for x in xrange(self.base, self.base+8)]
		return self.setall()

	def close(self):
		"""Reset all bits and close io[]"""
		# Bypass cache (self.bits) JIC
		for x in self.io: x.write(self.DEFAULT)
		self.clear()
		return self

	def set(self, b, v):
		"""Set bit #b to v if not previously set to that value"""
		if self.bits[b]!=v:
			self.io[b].write(v)
			self.io[b].flush()
			self.bits[b] = v
		return self

	def setall(self, v=None):
		"""Set all bits

		Defaults to turning them all to self.DEFAULT.
		"""
		if v==None: v = self.DEFAULT
		for b in xrange(8): self.set(b, v)
		return self

	def graph(self, f, min=0.0, max=1.0):
		"""Graph f on GPIO bits 2-7

		args:
		  f:   A float value to graph. But could be any numeric type.
		  min: The minimum value to display. Less than this all bits are off
		  max: The value at which the final bit is turned on.

		This method auto-opens the I/O if it hasn't been manually done.
		"""
		if not self.io: self.open() # auto-open for graphing
		s = (max-min)/5.0
		for b in xrange(2, 8):
			self.set(b, str(int(f<min)))
			min+=s
		return self



### Data to graph ###

def data():
	"""Get a data point for graphing.

	This implementation grabs the 1min LF
	"""
	return float(open('/proc/loadavg').read().split()[0])



### MAIN ###

if __name__=='__main__':
	import time
	leds = XIO()
	try:
		while 1:
			d = data()
			leds.graph(d, 0.1, 1.0)
			print '%.2f' % d, str(leds)[2:] # Just for verification. Can be removed.
			time.sleep(2) # sleep for 2sec
	finally: # Catch ^C and other crashes and reset the bits/LEDs
		leds.close()
