#!/usr/bin/env python3

import sys
import usb.util
import serial
import serial.tools.list_ports as ls
import time
import re
import csv

USAGE = """  <relay_number> <desired_state> [Interface] | list | file <file_path> [Interface]

	<relay_number>
		Number of the relay to control or "all" or"*" to set all relays to the same state.

	<desired_state>
		chose the state to set the relay "on/off or 1/0"

	[Interface]
		Specify the device to access.

	list
		List available ch340 relay

	file <file_path>
		Alow to give a pre-defined sequence to e specific relay in the form of a svg file in the forme "<relay_num>, <state>, [timeout]" eg :
		0, off   # set relay 0 off with no delay
		1, on, 3 # set relay 1 on and wait 3 secondes
		0, on	 # set relay 0 back on
		all, off # set all relay to off state
"""

def usage():
	print(sys.argv[0] + USAGE)


def list_relays():
	ports=list(ls.grep("1A86:7523"))
	print("available devices are :")
	for d,desc,hwid in ports :
		print(f"{d} {desc} {hwid}")

class RelayController:
	def __init__(self, port='', baud=9600):
		self.serial_port = None
		self.connected=False
		self.state = ['?'] * 4
		self.on_msgs = [bytes([160, i+1, 1, 162+i]) for i in range(4)]   # [ b'\xA0\x01\x01\xA2', b'\xA0\x02\x01\xA3', b'\xA0\x03\x01\xA4', b'\xA0\x04\x01\xA5' ]
		self.off_msgs = [bytes([160, i+1, 0, 161+i]) for i in range(4)]  # [ b'\xA0\x01\x00\xA1', b'\xA0\x02\x00\xA2', b'\xA0\x03\x00\xA3', b'\xA0\x04\x00\xA4' ]
		self.stat_msg = b'\xFF'
		self.alias_dic = {}

		if not port:
			ports=list(ls.grep("1A86:7523"))
			if len(ports) > 1 :
				print(f"Error: multiple device where detected ({len(ports)}) please specify de device.")
				list_relays()
				return
			if len(ports) < 1 :
				print("Error: no ch340 usb relay found (usb device vid:pid=1A86:7523)")
				return
			else :
				port,desc,hwid = ports[0]
				# print(f"using port : {port}")

		try:
			self.serial_port=serial.Serial(port, 9600)
			self.connected=True        
		except:
			print(f"Error: fail to connect {port}")
			
	def __del__(self):
		if self.connected:
			self.serial_port.close()

	def __set_state(self, num, state, time_out=0.1):
		if self.connected :
			n = int(num)
			if state == "on" or state == "1":
				self.serial_port.write(self.on_msgs[n])
				self.state[n] = '1'
			elif state == "off" or state == "0":
				self.serial_port.write(self.off_msgs[n])
				self.state[n] = '0'
			else :
				print(f"Error: Unknown command {state}")
				return -1
			time.sleep(time_out)
			return 0
		print(f"Error: device {self.serial_port.port} is not connected")
		return -1

	def set_state(self, num, state, time_out=0.1):
		rc = self.__set_state(num, state, time_out)
		print(f"{self.serial_port.port} set {num}   {state: <3} => {self.state} sleep {time_out}")
		return rc

	def set_all(self, state, time_out=0.1):
		if self.connected :
			for i in range(4) :
				rc = self.__set_state(i, state)
				if rc != 0 :
					return rc
			print(f"{self.serial_port.port} set all {state: <3} => {self.state} sleep {time_out}")
			time.sleep(time_out)
			return 0
		print(f"Error: device {self.serial_port.port} is not connected")
		return -1

	def execute_from_file(self, reader):
		if not self.connected :
			return -1
		for line in reader:
			line.replace("\n", "")
			# remove comments and white space at end of line
			line = re.sub("\s*#.*$|\s+$", "", line)
			# match include
			include = re.match("^\s*include\s+\"(.*)\"\s*$", line)
			if include :
				include_file, = include.groups()
				r = open(include_file)
				if not r :
					continue
				self.execute_from_file(r)
				r.close()
				continue
			# match alias
			alias = re.match("^\s*(\w+)\s*=\s*(\d)\s*$", line)
			if alias :
				val,key = alias.groups()
				self.alias_dic[val] = key
				continue
			# skype empty line
			if re.match("^\s*$", line) :
				continue
			# catch commands
			row = re.split("\s*[,;:\s]\s*", line)
			if len(row) < 2 or row[0] == "" or row[1] == "" :
				continue
			if row[0] in self.alias_dic :
				relay_num = self.alias_dic[row[0]]
			else :
				relay_num = row[0]
			relay_state = str(row[1])
			if len(row) > 2 and row[2] != "" :
				timeout = float(row[2])
			else:
				timeout = 0.1
			if relay_num == "*" or relay_num == "all":
				rc = self.set_all(relay_state, timeout)
			else :
				rc = self.set_state(relay_num, relay_state, timeout)
			if rc != 0 :
				return rc
		return 0


if len(sys.argv) == 2 and sys.argv[1] == "list":
	list_relays()
	sys.exit(0)

elif len(sys.argv) < 3 :
	print(sys.argv[0] + USAGE)
	sys.exit(1)

elif len(sys.argv) > 3 :
	relay_interface = sys.argv[3]

else :
	relay_interface = ""

dev = RelayController(relay_interface)
if not dev.connected :
	sys.exit(-1)

# Run using sequance file
if sys.argv[1] == "file" :
	if sys.argv[2] == "-" or sys.argv[2] == "" :
		reader = sys.stdin
	else :
		reader = open(sys.argv[2], 'r')
	if not reader :
		sys.exit(-1)
	rc = dev.execute_from_file(reader)
	reader.close()
	sys.exit(rc)

# Run same command for all relay
elif sys.argv[1] == "all" or sys.argv[1] == "*" :
	for i in range(4) :
		rc = dev.set_state(i, sys.argv[2])
		if rc != 0 :
			print(f"rc = {rc}")
			sys.exit(-1)
	sys.exit(0)

# Run single command
relay_num = sys.argv[1]
relay_state = sys.argv[2]
rc = dev.set_state(relay_num, relay_state)
sys.exit(rc)