#!/usr/bin/python3

import signal, json, time, argparse, sys, systemd.daemon, os, datetime
import paho.mqtt.client as mqtt

run_flag = True

class MemInfo():
	def __init__(self):
		self.total = 0
		self.free = 0
		self.used = 0
		self.available = 0
		self.active = 0
		self.swapfree = 0
		self.swaptotal = 0
		self.swapused = 0

	def update(self):
		with open('/proc/meminfo', 'r') as f:
			data = f.read().split('\n')
			for d in data:
				if 'MemTotal' in d:
					self.total = int(d.split()[1], 10) * 1024
				if 'MemFree' in d:
					self.free = int(d.split()[1], 10) * 1024
				if 'MemAvailable' in d:
					self.available = int(d.split()[1], 10) * 1024
				if 'SwapTotal' in d:
					self.swaptotal = int(d.split()[1], 10) * 1024
				if 'SwapFree' in d:
					self.swapfree = int(d.split()[1], 10) * 1024
			self.swapused = self.swaptotal - self.swapfree
			self.used = self.total - self.free
			self.active = self.total - self.available

class UpTime:
	def __init__(self):
		self.seconds = 0.0

	def update(self):
		with open('/proc/uptime', 'r') as f:
			data = f.read().split()
			self.seconds = float(data[0])

class LoadAverage:
	def __init__(self):
		self.avg1 = 0.0
		self.avg5 = 0.0
		self.avg15 = 0.0

	def update(self):
		with open('/proc/loadavg', 'r') as f:
			data = f.read().split()
			self.avg1 = float(data[0])
			self.avg5 = float(data[1])
			self.avg15 = float(data[2])

class Temperature:
	def __init__(self):
		self.temp0 = 0.0

	def update(self):
		with open('/sys/devices/virtual/thermal/thermal_zone0/temp', 'r') as f:
			data = f.read()
			self.temp0 = float(data) / 1000.0

class Battery:
	def __init__(self):
		self.capacity = 0
		self.power = 0.0

	def _conv_value(self, val):
		try:
			return int(val)
		except ValueError:
			return val

	def update(self):
		for x,y,z in os.walk('/sys/class/power_supply'):
			for s in y:
				ufile = os.path.join(x, s, 'uevent')
				with open(ufile, 'r') as uf:
					kv = dict([x.split('=') for x in uf.read().split('\n') if x != ''])
					kv.update((k, self._conv_value(v)) for k, v in kv.items())
					found_battery = False
					if kv['DEVTYPE'] == 'power_supply' and kv['POWER_SUPPLY_TYPE'] == 'Battery':
						if 'POWER_SUPPLY_SCOPE' in kv:
							if kv['POWER_SUPPLY_SCOPE'] != 'Device':
								found_battery = True
						else:
							found_battery = True
					if found_battery:
						if 'POWER_SUPPLY_CAPACITY' in kv:
							self.capacity = kv['POWER_SUPPLY_CAPACITY']
						if 'POWER_SUPPLY_VOLTAGE_NOW' in kv and 'POWER_SUPPLY_CURRENT_NOW' in kv:
							self.power = float(kv['POWER_SUPPLY_VOLTAGE_NOW']) / 1000000.0 * float(kv['POWER_SUPPLY_CURRENT_NOW']) / 1000000.0

def signal_handler(signal, frame):
    global run_flag
    print('Exit now')
    run_flag = False

def system_id(ethdev):
	with open('/sys/class/net/{}/address'.format(ethdev), 'r') as f:
		data = f.read().split(':')
		return ''.join(data[:3] + ['ff', 'fe'] + data[3:]).strip()

def tokenize_args():
	args = []
	for a in sys.argv[1:]:
		if ' ' in a:
			n = a.split(' ')
			for x in n:
				args.append(x)
		else:
			args.append(a)
	return args

if __name__ == '__main__':
	parser = argparse.ArgumentParser(description='Publish periodic sysinfo via mqtt', epilog='exit program by crtl-c')
	parser.add_argument('mqttbroker', help='hostname or IP address of MQTT broker to report to')
	parser.add_argument('period', help='reporting period in seconds', type=int)
	parser.add_argument('netdevice', help='netdevice name used for generating unique system ID')
	args = parser.parse_args(tokenize_args())

	if args.period < 1:
		print('period must be 1s or longer')
		sys.exit(1)

	signal.signal(signal.SIGINT, signal_handler)
	m = MemInfo()
	u = UpTime()
	l = LoadAverage()
	t = Temperature()
	b = Battery()

	sys_id = system_id(args.netdevice)
	client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
	client.connect(args.mqttbroker)
	print('connected to {} using id={}'.format(args.mqttbroker, sys_id))
	timeout = time.time() - args.period
	systemd.daemon.notify('READY=1')

	while run_flag:
		client.loop(timeout=1.0)
		if time.time() - timeout < args.period:
			time.sleep(1)
			systemd.daemon.notify('WATCHDOG=1')
		else:
			timeout = time.time()
			m.update()
			u.update()
			l.update()
			t.update()
			b.update()
			client.publish('/sysinfo/{}/status'.format(sys_id), '{}'.format(json.dumps(
				{
					"id": sys_id,
					"time": datetime.datetime.now(datetime.UTC).replace().isoformat(),
					"loadavg": l.avg1,
					"uptime": u.seconds,
					"temperature": t.temp0,
					"memory_used": m.used,
					"memory_free": m.free,
					"memory_active": m.active,
					"memory_swap_used": m.swapused,
					"memory_swap_free": m.swapfree,
					"battery_capacity": b.capacity,
					"battery_power": b.power
				}
			)))

	client.disconnect()
