An='page_size'
Am='content'
Al='message_history'
Ak='disconnect'
Aj=SystemExit
Ai=bytearray
AZ='addr'
AY='size'
AX='cleanup'
AW='%02X'
AV='big'
AU=ValueError
AJ='write memory'
AI='erase'
AH='signals'
AG='timestamp'
AF='(%9.6f) %s %02x#%s\n'
A8='utf-8'
A7='deviceChannel'
A6='busSpeed'
A3='enable_timer'
z=list
w='period'
s='title'
r='error_report'
q='messageMask'
p='messageFilter'
o='message'
n=range
m=len
i=str
e=Exception
c='ok'
b=print
Y='version'
X='response'
W='1.0.0'
T='dlc'
S='is_remote_frame'
R='is_extended_id'
N='error'
M=''
K='arbitration_id'
I='data'
G=int
F=False
C=True
A=None
from flask import Flask,request,redirect as Ao,render_template as Ap,url_for,jsonify
import logging as t
from werkzeug.utils import secure_filename as Aq
import io,time as Z,serial
from can import Message as A9
from flask_socketio import SocketIO as Ar,emit
import cantools as As,can,atexit,json as A4,copy as AK
from serial.tools import list_ports as At
import threading as Aa,os
BR=W
BS=C
AL=F
if AL==C:
	import collections as Au,inspect as Ab,re,struct as A0,sys,usb.core,usb.util,zlib,time as Z;A5=0.1;u=10000;BT=0;A1=1;BU=2;Av=3;Aw=4;BV=5;Ax=6;Ay=0;Az=1;Ac=2;A_=3;A2=4;x=5;B0=6;Ad=7;B1=8;Ae=9;B2=10;B3=33;B4={Ay:'STATE_APP_IDLE',Az:'STATE_APP_DETACH',Ac:'STATE_DFU_IDLE',A_:'STATE_DFU_DOWNLOAD_SYNC',A2:'STATE_DFU_DOWNLOAD_BUSY',x:'STATE_DFU_DOWNLOAD_IDLE',B0:'STATE_DFU_MANIFEST_SYNC',Ad:'STATE_DFU_MANIFEST',B1:'STATE_DFU_MANIFEST_WAIT_RESET',Ae:'STATE_DFU_UPLOAD_IDLE',B2:'STATE_DFU_ERROR'};Q=A;y=A;AM=A;j=0;B5=getattr(Ab,'getfullargspec',getattr(Ab,'getargspec',A))
	if'length'in B5(usb.util.get_string).args:
		def AN(dev,index):return usb.util.get_string(dev,255,index)
	else:
		def AN(dev,index):return usb.util.get_string(dev,index)
t.basicConfig(level=t.ERROR)
t.getLogger('socketio').setLevel(t.ERROR)
t.getLogger('engineio').setLevel(t.ERROR)
t.getLogger('werkzeug').setLevel(t.ERROR)
U=Aa.Lock()
v=Aa.Lock()
d=A
k=C
J=A
V=C
O=A
P=A
B=A
H=C
f=F
g='logs/'
h=M
AO=M
B6='/app/static'
B7='app/static'
B8='app/templates'
B9=M
AO='/canstudio'
B9='http://127.0.0.1:5000/'
L=Flask(__name__,static_url_path=B6,static_folder=B7,template_folder=B8)
L.config['SECRET_KEY']='secret!'
E=Ar(L,async_mode='threading',logger=F,engineio_logger=F)
a={A6:500000,'isExtended':F,A7:'/dev/ttyACM0',p:0,q:0}
AP='user_dbc.dbc'
AQ='user_dfu.dfu'
D={K:-1,R:F,S:F,T:0,I:[],w:-1,A3:F}
AA=1.0
def AB(db_obj,frame_id):
	try:db_obj.get_message_by_frame_id(frame_id)
	except e:return F
	return C
def AC(Input):
	A=Input
	while m(A)<8:A.append(0)
	return A
def BW(str):
	try:A=G(str,16)
	except:return F
	return C
def AD(int_list):A=[format(A,'02x').upper()for A in int_list];return M.join(A)
class BA:
	_BITRATES={10000:'S0',20000:'S1',50000:'S2',100000:'S3',125000:'S4',250000:'S5',500000:'S6',750000:'S7',1000000:'S8'};_SLEEP_AFTER_SERIAL_OPEN=0.25
	def __init__(B,channel,ttyBaudrate=500000,timeout=0,bitrate=A,**G):
		F=ttyBaudrate;E=bitrate;D=channel
		if not D:raise TypeError('Must specify a serial port.')
		if'@'in D:D,F=D.split('@')
		B.__esr_register=0;B.__esr_updated=C;B.buf=Ai();B.serial_port=serial.Serial(D,baudrate=F,timeout=timeout);Z.sleep(B._SLEEP_AFTER_SERIAL_OPEN)
		if E is not A:
			B.close()
			if E in B._BITRATES:B.write(B._BITRATES[E])
			else:raise AU('Invalid bitrate, choose one of '+', '.join(B._BITRATES)+'.')
		B.open();B.serial_port.reset_input_buffer();B.serial_port.reset_output_buffer()
	def get_error_register(A):return A.__esr_register
	def reset_error_register_updated(A):A.__esr_updated=F
	def get_error_register_updated(A):
		if A.__esr_updated:return C
		else:return F
	def write(B,string):
		A=string
		if not A.endswith('\r'):A+='\r'
		B.serial_port.write(A.encode(A8))
	def open(A):A.write('O')
	def close(A):A.write('C')
	def set_filter(D,c_filter):
		A=c_filter;A&=536870911;B=A.to_bytes(4,byteorder=AV);C='m'
		for E in n(0,m(B)):C+=AW%B[E]
		D.write(C)
	def set_mask(D,c_mask):
		A=c_mask;A&=536870911;B=A.to_bytes(4,byteorder=AV);C='M'
		for E in n(0,m(B)):C+=AW%B[E]
		D.write(C)
	def readline(A):
		F=b'\r';B=A.buf.find(F)
		if B>=0:E=A.buf[:B+1];A.buf=A.buf[B+1:];return E.decode(A8)
		while C:
			B=A.serial_port.in_waiting;D=A.serial_port.read(B);B=D.find(F)
			if B>=0:E=A.buf+D[:B+1];A.buf[0:]=D[B+1:];return E.decode(A8)
			else:A.buf.extend(D)
	def recv(K,timeout=A):
		N=timeout
		if N is not A:K.serial_port.timeout=N
		L=A;E=A;M=[];H=F;I=F;B=K.readline();D=0
		try:
			if m(B)>=9:
				if B[0]=='t'or B[0]=='T'or B[0]=='r'or B[0]=='R':
					O=B.split(B[0]);B=B[0]+O[-1]
					if B==A:return A
					else:
						if B[0]=='T':
							H=C;I=F;L=G(B[1:9],16);E=G(B[9:17],16);D=G(B[17])
							for J in n(0,D):M.append(G(B[18+J*2:20+J*2],16))
						elif B[0]=='t':
							H=F;I=F;L=G(B[1:9],16);E=G(B[9:12],16);D=G(B[12])
							for J in n(0,D):M.append(G(B[13+J*2:15+J*2],16))
						elif B[0]=='r':H=F;I=C;L=G(B[1:9],16);E=G(B[9:12],16);D=G(B[12])
						elif B[0]=='R':H=C;I=C;L=G(B[1:9],16);E=G(B[9:17],16);D=G(B[17])
						if E is not A:return A9(arbitration_id=E,timestamp=Z.perf_counter(),dlc=D,data=M,is_extended_id=H,is_remote_frame=I)
				elif B[0]=='E':K.__esr_register=G(B[1:9],16);K.__esr_updated=C;return A
				else:return A
			else:return A
		except e as P:b('recv',P,flush=C);b(B,flush=C);return A
	def send(C,msg,timeout=A):
		A=msg
		if A.is_remote_frame:
			if A.is_extended_id:B='R%08X%d'%(A.arbitration_id,A.dlc)
			else:B='r%03X%d'%(A.arbitration_id,A.dlc)
		else:
			if A.is_extended_id:B='T%08X%d'%(A.arbitration_id,A.dlc)
			else:B='t%03X%d'%(A.arbitration_id,A.dlc)
			for D in n(0,A.dlc):B+=AW%A.data[D]
		C.write(B)
	def read_buffer_pending(A):return A.serial_port.in_waiting
	def cleanup(A):
		try:A.close();A.serial_port.close()
		except e as B:b(AX,B,flush=C)
def BB():
	global H,B,h,f,g,U,D,AA,k,v,J
	while k==F:
		W=Z.perf_counter()
		if D[K]>-1 and D[w]>-1 and D[A3]==C:
			if B!=A and H:
				with L.app_context():
					P=A9(arbitration_id=D[K],timestamp=Z.perf_counter(),is_extended_id=D[R],is_remote_frame=D[S],dlc=D[T],data=D[I])
					with v:B.send(P,timeout=1)
					if f:
						if J!=A:
							with U:J.write(AF%(P.timestamp,'Tx',D[K],AD(D[I])))
					if D[I]!=A:
						X=AK.deepcopy(D[I]);Q=M
						if O!=A:
							try:
								if AB(O,D[K]):
									V=O.decode_message(D[K],AC(X))
									if not V is A:Q=A4.dumps(V,indent=4)
							except e as Y:
								if B!=A:B.cleanup();B=A
								with L.app_context():E.emit(r,{s:'rt',N:i(Y)})
						a={K:D[K],R:D[R],AG:P.timestamp,S:D[S],T:D[T],I:[G(A)for A in D[I]],AH:Q}
						if H:
							with L.app_context():E.emit('message_wo_history',a)
		b=Z.perf_counter();Z.sleep(AA-(b-W))
def BC():
	global H,B,O,h,g,f,U,J
	try:
		if B!=A:
			if B.read_buffer_pending()>0:
				D=B.recv()
				if D!=A:
					P=D.data;Q=M
					if O!=A:
						try:
							if AB(O,D.arbitration_id):
								V=O.decode_message(D.arbitration_id,AC(P))
								if not V is A:Q=A4.dumps(V,indent=4)
						except e as F:
							if B!=A:B.cleanup();B=A
							with L.app_context():E.emit(r,{s:'ct',N:i(F)})
					if f:
						if J!=A:
							with U:J.write(AF%(D.timestamp,'Rx',D.arbitration_id,AD(P)))
					with L.app_context():
						W={K:D.arbitration_id,R:D.is_extended_id,AG:D.timestamp,S:D.is_remote_frame,T:D.dlc,I:[G(A)for A in D.data],AH:Q}
						if H:
							with L.app_context():E.emit(o,W)
				if B.get_error_register_updated()==C:
					with L.app_context():E.emit('error_register_update',{'register':B.get_error_register()})
					B.reset_error_register_updated()
	except e as F:
		if B!=A:B.cleanup();B=A
		with L.app_context():E.emit(r,{s:'main',N:i(F)})
def BD():
	global H,B,P,a,V,h,g
	try:
		B=BA(a[A7],bitrate=a[A6])
		if B!=A:B.set_filter(a[p]);B.set_mask(a[q])
		while not V:BC()
		if B!=A:B.cleanup();B=A
	except e as C:
		if B!=A:B.cleanup();B=A
		with L.app_context():E.emit(r,{s:'background_thread',N:i(C)})
@E.on('connect')
def BX():A
@E.on(Ak)
def BY():A
@L.route('/')
def BZ():return Ao(AO)
@L.route(AO)
def Ba():return Ap('canstudio.html')
@E.on('configure_mask_filter')
def Bb(json_data):
	D=json_data;global B,H;F=W;I=M;E=z(D.keys())
	if p in E:a[p]=G(D[p]);A
	if q in E:a[q]=G(D[q]);A
	if B!=A and H==C:B.set_mask(a[q]);B.set_filter(a[p])
	return{X:H,N:I,Y:F}
@E.on('configure')
def Bc(json_data):
	D=json_data;global a,P,V,B;I=W;J=M;H=z(D.keys())
	if A6 in H:a[A6]=G(D[A6]);A
	if A7 in H:a[A7]=i(D[A7]);A
	if p in H:a[p]=G(D[p]);A
	if q in H:a[q]=G(D[q]);A
	V=C
	if P!=A:P.join()
	V=F;P=E.start_background_task(target=BD);return{X:c,N:J,Y:I}
@E.on('update_logging')
def Bd(json_data):
	D='EnableLogging';C='LogFileName';B=json_data;global f,h,g,J,U;E=W;F=M;B[o][C]=Aq(B[o][C]);f=B[o][D]
	with U:
		if J!=A:J.close();J=A
	if h!=B[o][C]:
		h=B[o][C]
		with U:
			if B[o][D]:
				if os.path.isdir(g):J=open(g+h,'w')
				else:os.mkdir(g);J=open(g+h,'w')
	return{X:c,N:F,Y:E}
@E.on('list_serial_ports')
def Be(json_data):
	C=W;D=M;E=At.comports();A={}
	for (F,G,B) in sorted(E):
		if'USB VID:PID=CAFE:374F'in i(B):A[F]={'desc':G,'hwid':B}
	return{X:c,N:D,Y:C,'available_ports':A}
@E.on('status')
def Bf(json_data):global H;A=W;B=M;return{X:H,N:B,Y:A}
@E.on('open_canbus')
def Bg(json_data):global H;A=W;B=M;H=C;return{X:c,N:B,Y:A}
def Bh(byte_array,byte_order=AV,signed=F):
	A=byte_array;B=[]
	for C in n(0,m(A)):B.append(G.from_bytes([A[C]],byte_order,signed=signed))
	return B
@E.on('transmit_dbc_message')
def Bi(json_data):
	Q=json_data;global O,H,B,h,f,g,U,v,J;m=W;V=M
	if O!=A:
		if Q!=A:
			j=z(Q.keys());D=O.get_message_by_name(j[0]);P=D.encode(Q[j[0]])
			if B!=A and H:
				with L.app_context():
					a=F
					if D.frame_id>2047:a=C
					b=F
					if D.length==0:b=C
					d=A9(arbitration_id=D.frame_id,timestamp=Z.perf_counter(),is_extended_id=a,is_remote_frame=b,dlc=D.length,data=P)
					with v:B.send(d,timeout=1)
					if f:
						if J!=A:
							with U:J.write(AF%(d.timestamp,'Tx',D.frame_id,AD(P)))
					if P!=A:
						n=AK.deepcopy(P);k=M
						if O!=A:
							try:
								if AB(O,D.frame_id):
									l=O.decode_message(D.frame_id,AC(n))
									if not l is A:k=A4.dumps(l,indent=4)
							except e as o:
								if B!=A:B.cleanup();B=A
								with L.app_context():E.emit(r,{s:'ct',N:i(o)})
						p={K:D.frame_id,R:a,AG:d.timestamp,S:b,T:D.length,I:[G(A)for A in P],AH:k}
						if H:
							with L.app_context():E.emit(Al,p)
		else:V+='No JSON data uploaded.'
	else:V+='No DBC element defined!'
	return{X:c,N:V,Y:m}
@E.on('update_dbc')
def Bj(json_data):
	global AP,O;G=W;H=M
	with open(AP,'w',encoding=A8)as D:D.write(json_data[I][Am]);D.close()
	O=As.database.load_file(AP);E=[];F=[];B={}
	for A in O.messages:
		F.append(A.name)
		if A.name not in z(B.keys()):B[A.name]={}
		for C in A.signals:E.append(C.name);B[A.name][C.name]=[C.minimum,C.maximum]
	J=A4.dumps(B,indent=4);return{X:c,N:H,Y:G,'signal_names':','.join(E),'node_names':','.join(F),'node_signals':J}
@E.on('clear_dbc')
def Bk(json_data):global O;B=W;C=M;O=A;return{X:c,N:C,Y:B}
@E.on('flash_firmware')
def Bl(json_data):
	global H,B,P,V,AQ,AL;L=W;D=M
	if AL==C:
		try:
			with open(AQ,'wb')as G:G.write(json_data[I][Am]);G.close()
			V=C
			if P!=A:P.join()
			V=F
			if B!=A and H==C:B.cleanup();H=F
			E={};E['idVendor']=1155;E['idProduct']=57105;BE(**E);BH();J=C;K=BN(AQ)
			if not K:D+='No data in dfu file\n';J=F
			if J==C:BP(K,C,progress=BQ);BK()
		except e as O:D+=i(O)+'\n'
	return{X:c,N:D,Y:L}
@E.on('close_canbus')
def Bm(json_data):
	global H,B,P,V;D=W;E=M;V=C;G=C
	if P!=A:P.join()
	if d!=A:d.join()
	G=F;V=F
	if B!=A and H==C:B.cleanup();H=F
	return{X:c,N:E,Y:D}
@E.on('cleanup_system')
def Bn(json_data):global H,B,P,V;A=W;C=M;cleanup();return{X:c,N:C,Y:A}
@E.on('request_device_errors')
def Bo(json_data):
	global H,B,v;D=W;E=M
	if B!=A and H==C:
		with v:B.write('F')
	return{X:c,N:E,Y:D}
@E.on('transmit')
def Bp(json_data):
	D=json_data;global H,B,h,f,g,U,v,J;b=W;d=M;P=z(D.keys());C={K:-1,R:F,S:F,T:0,I:[]}
	if K in P:C[K]=D[K]
	if R in P:C[R]=D[R]
	if T in P:C[T]=D[T]
	if S in P:C[S]=D[S]
	if I in P:C[I]=D[I]
	if C[K]>-1:
		if B!=A and H:
			with L.app_context():
				Q=A9(arbitration_id=C[K],timestamp=Z.perf_counter(),is_extended_id=C[R],is_remote_frame=C[S],dlc=C[T],data=C[I])
				with v:B.send(Q,timeout=1)
				if f:
					if J!=A:
						with U:J.write(AF%(Q.timestamp,'Tx',C[K],AD(C[I])))
				if C[I]!=A:
					j=AK.deepcopy(C[I]);V=M
					if O!=A:
						try:
							if AB(O,C[K]):
								a=O.decode_message(C[K],AC(j))
								if not a is A:V=A4.dumps(a,indent=4)
						except e as k:
							if B!=A:B.cleanup();B=A
							with L.app_context():E.emit(r,{s:'ct',N:i(k)})
					l={K:C[K],R:C[R],AG:Q.timestamp,S:C[S],T:C[T],I:[G(A)for A in C[I]],AH:V}
					if H:
						with L.app_context():E.emit(Al,l)
	return{X:c,N:d,Y:b,o:C}
@E.on('repeated_transmit')
def Bq(json_data):
	G=json_data;global B,h,f,g,U,D,d,AA,k;J=W;L=M;H=z(G.keys());D[K]=-1;D[R]=F;D[S]=F;D[T]=0;D[I]=[];D[w]=-1
	if K in H:D[K]=G[K]
	if R in H:D[R]=G[R]
	if w in H:D[w]=G[w]
	if T in H:D[T]=G[T]
	if S in H:D[S]=G[S]
	if I in H:D[I]=G[I]
	if A3 in H:D[A3]=G[A3]
	if d!=A:k=C;d.join();k=F
	if D[w]>-1 and D[K]>-1:
		AA=D[w]/1000.0
		if D[A3]:k=F;d=E.start_background_task(target=BB)
	return{X:c,N:L,Y:J,o:D}
def cleanup():
	global B,V,P,d,k,J,U
	try:
		V=C;k=C
		if P!=A:P.join()
		if B!=A:B.cleanup();B=A
		if d!=A:d.join()
		with U:
			if J!=A:J.close();J=A
	except e as D:
		if B!=A:B.cleanup();B=A
		with L.app_context():E.emit(r,{s:AX,N:i(D)})
@atexit.register
def Br():
	global B,V,P,d,k,J,U
	try:
		with L.app_context():E.emit(Ak,{o:'Server is disconnecting.'})
		V=C;k=C
		if P!=A:P.join()
		if B!=A:B.cleanup();B=A
		if d!=A:d.join()
		with U:
			if J!=A:J.close();J=A
	except e as D:
		if B!=A:B.cleanup();B=A
		with L.app_context():E.emit(r,{s:AX,N:i(D)})
def Af(descr):
	B=descr
	if m(B)==9 and B[0]==9 and B[1]==B3:C=Au.namedtuple('CfgDescr',['bLength','bDescriptorType','bmAttributes','wDetachTimeOut','wTransferSize','bcdDFUVersion']);return C(*A0.unpack('<BBBHHH',Ai(B)))
	return A
def BE(**E):
	'Initializes the found DFU device so that we can program it.';global Q,y;B=Ag(**E)
	if not B:raise AU('No DFU device found')
	if m(B)>1:raise AU('Multiple DFU devices found')
	Q=B[0];Q.set_configuration();usb.util.claim_interface(Q,j);y=A
	for D in Q.configurations():
		y=Af(D.extra_descriptors)
		if y:break
		for F in D.interfaces():
			y=Af(F.extra_descriptors)
			if y:break
	for G in n(4):
		C=AE()
		if C==Ac:break
		elif C==x or C==Ae:BF()
		else:BG()
def BF():'Sends an abort request.';Q.ctrl_transfer(33,Ax,0,j,A,u)
def BG():'Clears any error status (perhaps left over from a previous session).';Q.ctrl_transfer(33,Aw,0,j,A,u)
def AE():
	'Get the status of the last operation.';A=Q.ctrl_transfer(161,Av,0,j,6,20000)
	if A[5]:
		B=AN(Q,A[5])
		if B:b(B)
	return A[4]
def l(stage,expected):
	B=expected;D=100;Z.sleep(A5);A=AE();C=0
	while A!=B:
		Z.sleep(A5);A=AE();C+=1
		if C>=D:break
	if A!=B:raise Aj('DFU: %s failed (%s)'%(stage,B4.get(A,A)))
def BH():'Performs a MASS erase (i.e. erases the entire device).';Q.ctrl_transfer(33,A1,0,j,'A',u);l(AI,A2);l(AI,x)
def BI(addr):
	'Erases a single page.'
	if AM:b('Erasing page: 0x%x...'%addr)
	A=A0.pack('<BI',65,addr);Q.ctrl_transfer(33,A1,0,j,A,u);l(AI,A2);l(AI,x)
def AR(addr):'Sets the address for the next operation.';A='set address';B=A0.pack('<BI',33,addr);Q.ctrl_transfer(33,A1,0,j,B,u);l(A,A2);l(A,x)
def BJ(addr,buf,progress=A,progress_addr=0,progress_size=0):
	'Writes a buffer into memory. This routine assumes that memory has\n    already been erased.\n    ';F=progress_addr;E=progress;B=0;A=0;C=m(buf);D=addr
	while A<C:
		if AM and B%512==0:b('Addr 0x%x %dKBs/%dKBs...'%(D+A,A//1024,C//1024))
		if E and B%2==0:E(F,D+A-F,progress_size)
		AR(D+A);Z.sleep(A5);G=min(y.wTransferSize,C-A);Q.ctrl_transfer(33,A1,2,j,buf[A:A+G],u);l(AJ,A2);l(AJ,x);B+=1;A+=G
def Bs(buf,xfer_offset):
	'Writes a single page. This routine assumes that memory has already\n    been erased.\n    ';A=xfer_offset;B=134217728;AR(B+A);Z.sleep(A5);Q.ctrl_transfer(33,A1,2,j,buf,u);l(AJ,A2);l(AJ,x)
	if AM:b('Write: 0x%x '%(B+A))
def BK():
	'Exit DFU mode, and start running the program.';AR(134217728);Z.sleep(A5*5);Q.ctrl_transfer(33,A1,0,j,A,u)
	try:
		if AE()!=Ad:b('Failed to reset device')
		usb.util.dispose_resources(Q)
	except:pass
def AS(values,names):'Creates a dict with `names` as fields, and `values` as values.';return dict(zip(names.split(),values))
def AT(fmt,data,names):'Parses the struct defined by `fmt` from `data`, stores the parsed fields\n    into a named tuple using `names`. Returns the named tuple, and the data\n    with the struct stripped off.';A=A0.calcsize(fmt);return AS(A0.unpack(fmt,data[:A]),names),data[A:]
def BL(string):'Extracts a null-terminated string from a byte array.';return string.decode(A8).split('\x00',1)[0]
def BM(data):'Computes the CRC32 value for the data passed in.';return 4294967295&-zlib.crc32(data)-1
def BN(filename):
	'Reads a DFU file, and parses the individual elements from the file.\n    Returns an array of elements. Each element is a dictionary with the\n    following keys:\n        num     - The element index.\n        address - The address that the element data should be written to.\n        size    - The size of the element data.\n        data    - The element data.\n    If an error occurs while parsing the file, then None is returned.\n    ';P='num';F='name'
	with open(filename,'rb')as Q:A=Q.read()
	G=BM(A[:-4]);H=[];R,A=AT('<5sBIB',A,'signature version size targets')
	for J in n(R['targets']):
		B,A=AT('<6sBI255s2I',A,'signature altsetting named name size elements');B[P]=J
		if B['named']:B[F]=BL(B[F])
		else:B[F]=M
		K=B[AY];C=A[:K];A=A[K:]
		for S in n(B['elements']):D,C=AT('<2I',C,'addr size');D[P]=S;O=D[AY];T=C[:O];C=C[O:];D[I]=T;H.append(D)
		if m(C):
			with L.app_context():E.emit(r,{s:'read_dfu',N:'target %d PARSE ERROR'%J})
	U=AS(A0.unpack('<4H3sBI',A[:16]),'device product vendor dfu ufd len crc')
	if G!=U['crc']:b('CRC ERROR: computed crc32 is 0x%08x'%G);return
	A=A[16:]
	if A:b('PARSE ERROR');return
	return H
class BO:
	'Class for filtering USB devices to identify devices which are in DFU\n    mode.\n    '
	def __call__(C,device):
		for B in device:
			for A in B:return A.bInterfaceClass==254 and A.bInterfaceSubClass==1
def Ag(*A,**B):'Returns a list of USB devices which are currently in DFU mode.\n    Additional filters (like idProduct and idVendor) can be passed in\n    to refine the search.\n    ';return z(usb.core.find(*(A),find_all=C,custom_match=BO(),**B))
def Ah(device):
	'Returns an array which identifies the memory layout. Each entry\n    of the array will contain a dictionary with the following keys:\n        addr        - Address of this memory segment.\n        last_addr   - Last address contained within the memory segment.\n        size        - Size of the segment, in bytes.\n        num_pages   - Number of pages in the segment.\n        page_size   - Size of each page, in bytes.\n    ';F=device;L=F[0];M=L[(0,0)];N=AN(F,M.iInterface);B=N.split('/');H=[]
	for I in n(1,m(B),2):
		C=G(B[I],0);O=B[I+1].split(',');P=re.compile('(\\d+)\\*(\\d+)(.)(.)')
		for Q in O:
			D=P.match(Q);J=G(D.groups()[0],10);A=G(D.groups()[1],10);K=D.groups()[2]
			if K=='K':A*=1024
			if K=='M':A*=1024*1024
			E=J*A;R=C+E-1;H.append(AS((C,R,E,J,A),'addr last_addr size num_pages page_size'));C+=E
	return H
def Bt(*D,**E):
	'Prints a list of devices detected in DFU mode.';C=Ag(*(D),**E)
	if not C:raise Aj('No DFU capable devices found')
	for A in C:
		b('Bus {} Device {:03d}: ID {:04x}:{:04x}'.format(A.bus,A.address,A.idVendor,A.idProduct));F=Ah(A);b('Memory Layout')
		for B in F:b('    0x{:x} {:2d} pages of {:3d}K bytes'.format(B[AZ],B['num_pages'],B[An]//1024))
def BP(elements,mass_erase_used,progress=A):
	'Writes the indicated elements into the target memory,\n    erasing as needed.\n    ';C=progress;M=Ah(Q)
	for G in elements:
		A=G[AZ];D=G[AY];H=G[I];E=D;F=A
		if C and E:C(F,0,E)
		while D>0:
			B=D
			if not mass_erase_used:
				for J in M:
					if A>=J[AZ]and A<=J['last_addr']:
						K=J[An];L=A&~(K-1)
						if A+B>L+K:B=L+K-A
						BI(L);break
			BJ(A,H[:B],C,F,E);H=H[B:];A+=B;D-=B
			if C:C(F,A-F,E)
def BQ(addr,offset,size):
	'Prints a progress report suitable for use on the command line.'
	with L.app_context():E.emit('cli_progress',{'progress':offset*100//size})
if __name__=='__main__':E.run(L,debug=F)