#!/usr/bin/python
# Resource monitor - client-side daemon
#
# Uses psutil to monitor system resources, and updates the data over USB using raw HID packets
# to the hardware monitor.
#
########################################################

import hid		#https://github.com/apmorton/pyhidapi
import psutil	#https://github.com/giampaolo/psutil
import pynvml	#https://pypi.python.org/pypi/nvidia-ml-py/
import sensors	#https://pypi.python.org/pypi/PySensors/

import os
import sys
import time

buf = list("\x01CXXXXMX RXWXUXDXRXHXDXBX  dd\xDFdd\xDF")
dev = 0

def main():
	state = {
		"cpu": [], "memory": [],
		"gpu": [], "gpu_memory": [], "gpu_count": pynvml.nvmlDeviceGetCount(),
		"disk_read": [], "disk_write": [], "disk_read_last": psutil.disk_io_counters().read_bytes, "disk_write_last": psutil.disk_io_counters().write_bytes,
		"net_upload": [], "net_download": [], "net_upload_last": psutil.net_io_counters().bytes_sent, "net_download_last": psutil.net_io_counters().bytes_recv
	}
	
	temp_time = 0		#0 shows temperature, 1 shows time
	timer = 0
	
	while True:
		exception = False
		
		#CPU Usage:
		state["cpu"].append(int(psutil.cpu_percent() / 11));
		if (len(state["cpu"]) > 300):
			state["cpu"].pop(0)
		last10 = state["cpu"][-10:]
		last60 = state["cpu"][-60:]
		
		buf[2] = writeBarGraph(state["cpu"][-1])
		buf[3] = writeBarGraph(sum(last10) / len(last10))
		buf[4] = writeBarGraph(sum(last60) / len(last60))
		buf[5] = writeBarGraph(sum(state["cpu"]) / len(state["cpu"]))
		
		#Memory Usage:
		state["memory"].append(int(psutil.virtual_memory().percent / 11))
		if (len(state["memory"]) > 10):
			state["memory"].pop(0)
		buf[7] = writeBarGraph(state["memory"][-1])
		buf[8] = writeBarGraph(sum(state["memory"]) / len(state["memory"]))
		
		#Disk I/O:
		disk = psutil.disk_io_counters()

		state["disk_read"].append(disk.read_bytes - state["disk_read_last"])
		state["disk_read_last"] = disk.read_bytes
		state["disk_write"].append(disk.write_bytes - state["disk_write_last"])
		state["disk_write_last"] = disk.write_bytes

		if (len(state["disk_read"]) > 10):
			state["disk_read"].pop(0)
		if (len(state["disk_write"]) > 10):
			state["disk_write"].pop(0)
			
		buf[10] = writeBarGraph(scaleDisk(sum(state["disk_read"]) / len(state["disk_read"])))
		buf[12] = writeBarGraph(scaleDisk(sum(state["disk_write"]) / len(state["disk_write"])))
		
		
		#Network I/O:
		net = psutil.net_io_counters()
		
		state["net_download"].append(net.bytes_recv - state["net_download_last"])
		state["net_download_last"] = net.bytes_recv
		state["net_upload"].append(net.bytes_sent - state["net_upload_last"])
		state["net_upload_last"] = net.bytes_sent
		
		if (len(state["net_download"]) > 10):
			state["net_download"].pop(0)
		if (len(state["net_upload"]) > 10):
			state["net_upload"].pop(0)
			
		buf[14] = writeBarGraph(scaleNetwork(sum(state["net_upload"]) / len(state["net_upload"])))
		buf[16] = writeBarGraph(scaleNetwork(sum(state["net_download"]) / len(state["net_download"])))


		if temp_time == 0:
			#CPU Temperature:
			for chip in sensors.iter_detected_chips():
				for feature in chip:
					if (feature.label == "Physical id 0"):
						cpu_temp = int(feature.get_value())
						if (cpu_temp > 99):
							cpu_temp = 99
						buf[27] = str(cpu_temp / 10)
						buf[28] = str(cpu_temp % 10)
						buf[29] = '\xDF'
			
			#GPU Temperature:
			gpuHandle = pynvml.nvmlDeviceGetHandleByIndex(0)	#If you have multiple GPUs, iterate over them here and average the results
			gpu_temp = pynvml.nvmlDeviceGetTemperature(gpuHandle, pynvml.NVML_TEMPERATURE_GPU)
			if (gpu_temp > 99):
				gpu_temp = 99
			buf[30] = str(gpu_temp / 10)
			buf[31] = str(gpu_temp % 10)
			buf[32] = '\xDF'
		else:
			time_string = time.strftime("%I:%M%p")
			if time_string[0] != '0':
				buf[27] = time_string[0]
			else:
				buf[27] = ' '
			buf[28] = time_string[1]
			buf[29] = time_string[2]
			buf[30] = time_string[3]
			buf[31] = time_string[4]
			buf[32] = time_string[5]

		#Rotate through mount points and show percentage
		buf[18] = writeBarGraph(int(psutil.disk_usage("/").percent / 12.5))
		buf[20] = writeBarGraph(int(psutil.disk_usage("/home").percent / 12.5))
		buf[22] = writeBarGraph(int(psutil.disk_usage("/mnt/data").percent / 12.5))
		buf[24] = writeBarGraph(int(psutil.disk_usage("/mnt/backup").percent / 12.5))
		
		timer = timer + 1
		if (timer >= 1):
			timer = 0;
			temp_time = temp_time + 1
			if (temp_time > 1):
				temp_time = 0;

		sendData()

		time.sleep(1)

def sendData():
	dev.write(''.join(buf))

def scaleNetwork(value):
	if (value >   0x1000000):
		return 9
	elif (value > 0x400000):
		return 8
	elif (value > 0x100000):
		return 7
	elif (value > 0x40000):
		return 6
	elif (value > 0x10000):
		return 5
	elif (value > 0x4000):
		return 4
	elif (value > 0x1000):
		return 3
	elif (value > 0x400):
		return 2
	elif (value > 0x100):
		return 1
	else:
		return 0
	
def scaleDisk(value):
	if (value >   0x10000000):
		return 9
	elif (value > 0x4000000):
		return 8
	elif (value > 0x1000000):
		return 7
	elif (value > 0x400000):
		return 6
	elif (value > 0x100000):
		return 5
	elif (value > 0x40000):
		return 4
	elif (value > 0x10000):
		return 3
	elif (value > 0x4000):
		return 2
	elif (value > 0x1000):
		return 1
	else:
		return 0

def writeBarGraph(value):
	if (value <= 0):
		return ' '
	elif (value >= 8):
		return '\xff'
	else:
		return chr(value)

########## Main startup hooks here ##########
if (__name__=="__main__"):
	pynvml.nvmlInit()
	sensors.init()

	try:
		while (True):
			try:
				dev = hid.Device(vid=0x4200, pid=0xFF00)
				main()
			except hid.HIDException:
				sleep(1)
			finally:
				if ("close" in dir(dev)):
					dev.close()
	except:
		raise
	finally:
		pynvml.nvmlShutdown()
		sensors.cleanup()
